def _forney(self, omega, X): """Computes the error magnitudes""" # XXX Is floor division okay here? Should this be ceiling? s = (self.n - self.k) // 2 Y = [] for l, Xl in enumerate(X): # Compute the first part of Yl Yl = Xl**s Yl *= omega.evaluate( Xl.inverse() ) Yl *= Xl.inverse() # Compute the sequence product and multiply its inverse in prod = GF256int(1) for ji in xrange(s): if ji == l: continue if ji < len(X): Xj = X[ji] else: Xj = GF256int(0) prod = prod * (Xl - Xj) Yl = Yl * prod.inverse() Y.append(Yl) return Y
def _forney(self, omega, X): """Вычисляем величины ошибок""" s = (self.n - self.k) // 2 Y = [] for l, Xl in enumerate(X): # Вычисляем первую часть Yl Yl = Xl**s Yl *= omega.evaluate(Xl.inverse()) Yl *= Xl.inverse() # Вычисляем произведение последовательности и и производим обратное умножение prod = GF256int(1) for ji in xrange(s): if ji == l: continue if ji < len(X): Xj = X[ji] else: Xj = GF256int(0) prod = prod * (Xl - Xj) Yl = Yl * prod.inverse() Y.append(Yl) return Y
def test_arithmetic(self): a = GF256int(3) b = GF256int(9) self.assertEqual(a + b, 10) self.assertEqual(b + a, 10) self.assertEqual(3 + b, 10) self.assertEqual(a + 9, 10) self.assertEqual(a - b, 10) self.assertEqual(b - a, 10) self.assertEqual(3 - b, 10) self.assertEqual(a - 9, 10) self.assertEqual(a * b, 27) self.assertEqual(b * a, 27) self.assertEqual(3 * b, 27) self.assertEqual(a * 9, 27) self.assertEqual(b * b.inverse(), 1) self.assertEqual(b / b, 1) self.assertEqual(b / a, 7) self.assertEqual(9 / a, 7) self.assertEqual(b / 3, 7) self.assertRaises(Exception, lambda: b**a) self.assertEqual(b**3, 127) self.assertRaises(Exception, lambda: a**b) self.assertEqual(a**9, 46) self.assertEqual(b.inverse(), 79) self.assertEqual(b * 79, 1)
def _chien_search(self, sigma): """Recall the definition of sigma, it has s roots. To find them, this function evaluates sigma at all 255 non-zero points to find the roots The inverse of the roots are X_i, the error locations Returns a list X of error locations, and a corresponding list j of error positions (the discrete log of the corresponding X value) The lists are up to s elements large. Important technical math note: This implementation is not actually Chien's search. Chien's search is a way to evaluate the polynomial such that each evaluation only takes constant time. This here simply does 255 evaluations straight up, which is much less efficient. """ X = [] j = [] p = GF256int(3) for l in xrange(1,256): # These evaluations could be more efficient, but oh well if sigma.evaluate( p**l ) == 0: X.append( p**(-l) ) # This is different than the notes, I think the notes were in error # Notes said j values were just l, when it's actually 255-l j.append(255 - l) return X, j
def test_div_scalar2(self): """Test that dividing by a scalar is the same as multiplying by the scalar's inverse""" a = Polynomial(map(GF256int, (5, 3, 1, 1, 6, 8))) scalar = GF256int(50) self.assertEqual(a * Polynomial(x0=scalar), a // Polynomial(x0=scalar.inverse()))
def _syndromes(self, r): """Given the received codeword r in the form of a Polynomial object, computes the syndromes and returns the syndrome polynomial """ n = self.n k = self.k # s[l] is the received codeword evaluated at α^l for 1 <= l <= s # α in this implementation is 3 s = [GF256int(0)] # s[0] is 0 (coefficient of z^0) for l in xrange(1, n-k+1): s.append( r.evaluate( GF256int(3)**l ) ) # Now build a polynomial out of all our s[l] values # s(z) = sum(s_i * z^i, i=1..inf) sz = Polynomial( reversed( s ) ) return sz
def _syndromes(self, r): """С учетом принятого кодового слова r в виде полиномиального объекта, вычисляет синдромы и возвращает синдромы в полином """ n = self.n k = self.k # s[l] принятое кодовое слово оценивается α^l при 1 <= l <= s # α здесь равен 3 s = [GF256int(0)] # s[0] равен 0 (коэффициент z^0) for l in xrange(1, n - k + 1): s.append(r.evaluate(GF256int(3)**l)) # Теперь стоим многочлен из всех наших s[l] значений # s(z) = sum(s_i * z^i, i=1..inf) sz = Polynomial(reversed(s)) return sz
def test_div_scalar(self): """Tests division by a scalar""" numbers = map(GF256int, (5, 20, 50, 100, 134, 158, 0, 148, 233, 254, 4, 5, 2)) scalar = GF256int(17) poly = Polynomial(numbers) scalarpoly = Polynomial(x0=scalar) self.assertEqual((poly // scalarpoly).coefficients, tuple(map(lambda x: x / scalar, numbers)))
def encode(self, message, poly=False): """Кодирует данную строку кодировкой Рида-Соломона. Возвращает байт строка с сообщением k байт и n-k четности байтов в конце. Если сообщение меньше k байт, то предполагается, что необходимо дополнить в начале нулевыми байтами. Последовательность возвращает всегда n байт. Если poly не является ложным, возвращаем закодированный полиномиальный объект вместо многочлена и переводим обратно в строку (полезно для отладки) """ n = self.n k = self.k if len(message) > k: raise ValueError( "Длина сообщения не более %d. Длина сообщения %d" % (k, len(message))) # Кодирование сообщения в виде полинома: m = Polynomial(GF256int(ord(x)) for x in message) # Сдвиг полинома до n-k путем умножения на x^(n-k) mprime = m * Polynomial((GF256int(1), ) + (GF256int(0), ) * (n - k)) # mprime = q*g + b для некоторого q # найдем b: b = mprime % self.g # Вычитаем b, имеем c = q*g c = mprime - b # Так как c кратен g, то он имеет n-k корней: α^1 тогда # α^(n-k) if poly: return c # Переведем полином обратно в строку return "".join(chr(x) for x in c.coefficients).rjust(n, "\0")
def encode(self, message, poly=False): """Encode a given string with reed-solomon encoding. Returns a byte string with the k message bytes and n-k parity bytes at the end. If a message is < k bytes long, it is assumed to be padded at the front with null bytes. The sequence returned is always n bytes long. If poly is not False, returns the encoded Polynomial object instead of the polynomial translated back to a string (useful for debugging) """ n = self.n k = self.k if len(message)>k: raise ValueError("Message length is max %d. Message was %d" % (k, len(message))) # Encode message as a polynomial: m = Polynomial(GF256int(ord(x)) for x in message) # Shift polynomial up by n-k by multiplying by x^(n-k) mprime = m * Polynomial((GF256int(1),) + (GF256int(0),)*(n-k)) # mprime = q*g + b for some q # so let's find b: b = mprime % self.g # Subtract out b, so now c = q*g c = mprime - b # Since c is a multiple of g, it has (at least) n-k roots: α^1 through # α^(n-k) if poly: return c # Turn the polynomial c back into a byte string return "".join(chr(x) for x in c.coefficients).rjust(n, "\0")
def verify(self, code): """Проверяем правильность кода путем теста кода в виде полинома код делим на g проверяем да/нет """ n = self.n k = self.k h = self.h g = self.g c = Polynomial(GF256int(ord(x)) for x in code) # Это тоже работает, но занимает больше времени. Обе проверки подходят #возвращаем (c*h)%gtimesh == Polynomial(x0=0). # Поскольку кодовое слово кратно g, проверяем этот код деля на g #этого достаточно для проверки кодового слова. return c % g == Polynomial(x0=0)
def verify(self, code): """Verifies the code is valid by testing that the code as a polynomial code divides g returns True/False """ n = self.n k = self.k h = self.h g = self.g c = Polynomial(GF256int(ord(x)) for x in code) # This works too, but takes longer. Both checks are just as valid. #return (c*h)%gtimesh == Polynomial(x0=0) # Since all codewords are multiples of g, checking that code divides g # suffices for validating a codeword. return c % g == Polynomial(x0=0)
def __init__(self, n, k): """Creates a new Reed-Solomon Encoder/Decoder object configured with the given n and k values. n is the length of a codeword, must be less than 256 k is the length of the message, must be less than n The code will have error correcting power s where 2s = n - k The typical RSCoder is RSCoder(255, 223) """ if n < 0 or k < 0: raise ValueError("n and k must be positive") if not n < 256: raise ValueError("n must be at most 255") if not k < n: raise ValueError("Codeword length n must be greater than message" " length k") self.n = n self.k = k # Generate the generator polynomial for RS codes # g(x) = (x-α^1)(x-α^2)...(x-α^(n-k)) # α is 3, a generator for GF(2^8) g = Polynomial((GF256int(1),)) for alpha in xrange(1,n-k+1): p = Polynomial((GF256int(1), GF256int(3)**alpha)) g = g * p self.g = g # h(x) = (x-α^(n-k+1))...(x-α^n) h = Polynomial((GF256int(1),)) for alpha in xrange(n-k+1,n+1): p = Polynomial((GF256int(1), GF256int(3)**alpha)) h = h * p self.h = h # g*h is used in verification, and is always x^n-1 # TODO: This is hardcoded for (255,223) # But it doesn't matter since my verify method doesn't use it self.gtimesh = Polynomial(x255=GF256int(1), x0=GF256int(1))
def __init__(self, n, k): """Создает новый объект Рида-Соломона кодер/декодер, с присваиваемыми значениями n и k где, n - длина кодового слова, должна быть меньше, чем 256 k - длина сообщения, должна быть меньше, чем n Код будет иметь мощность корректировки ошибки S, где 2S = n - k """ if n < 0 or k < 0: raise ValueError("n и k должны быть положительными") if not n < 256: raise ValueError("n должна быть не более 256") if not k < n: raise ValueError( "Длина кодового слова n должна быть больше чем длина сообщения" ) self.n = n self.k = k # Генерируем образующий полином для RS кодера # g(x) = (x-α^1)(x-α^2)...(x-α^(n-k)) # α is 3, a генерируется в GF(2^8) g = Polynomial((GF256int(1), )) for alpha in xrange(1, n - k + 1): p = Polynomial((GF256int(1), GF256int(3)**alpha)) g = g * p self.g = g # h(x) = (x-α^(n-k+1))...(x-α^n) h = Polynomial((GF256int(1), )) for alpha in xrange(n - k + 1, n + 1): p = Polynomial((GF256int(1), GF256int(3)**alpha)) h = h * p self.h = h # g*h используется в проверке, и всегда x^n-1 self.gtimesh = Polynomial(x255=GF256int(1), x0=GF256int(1))
def _chien_search(self, sigma): """Напомним определение sigma , она имеет s корней. Для их нахождения, эта функция оценивает sigma на всех 255 ненулевых точках чтобы найти корни Обратные корни X_i, это места ошибок. Возвращает список X места ошибок, и соответствующий список j позиции ошибок(дескретная журнала соответствует значению X). Списки заполняются до s элемента. Важно техническая математика примечание: эта реализация на самом деле не поиск члена. Поиск члена это такой способ оценить полином что каждая оценка делается только в определенное время. Здесь просто делается 255 оценок, что гораздо менее эффективно. """ X = [] j = [] p = GF256int(3) for l in xrange(1, 256): # Эти оценки могут быть более эффективными if sigma.evaluate(p**l) == 0: X.append(p**(-l)) j.append(255 - l) return X, j
def test_fermats_theorem(self): for x in range(1, 256): self.assertEqual(GF256int(x)**255, 1)
def decode(self, r, nostrip=False): """Принимаем полученную строку или массив байт r и пытаемся расшифровать. Если это действующее кодовое слово, или если не содержит более (n-k)/2 ошибок, сообщение возвращается. Сообщение всегда имеет k байт, если сообщение меньше то оно дополняется нулевыми байтами. При декодировании отделим эти ведущие нулевые байты но это может вывать проблемы при декодировании двоичных данных. Когда nostrip истина, сообщение всегда возвращает k байт. Это полезно и позволяет убедиться, что нету потерянных данных при декодировании. """ n = self.n k = self.k if self.verify(r): # Последние n-k байты четности if nostrip: return r[:-(n - k)] else: return r[:-(n - k)].lstrip("\0") #### Возвращаем r в полином r = Polynomial(GF256int(ord(x)) for x in r) # Вычисляем синдромы: sz = self._syndromes(r) # Найдем полином описывающий положение ошибки и полином оценивающий # ошибку используя алгоритм Берлекампа-Месси sigma, omega = self._berlekamp_massey(sz) # Now use Chien's procedure для нахождения позиции ошибки # j это массив целых чисел, представляющий позиции ошибок, 0 # означает самый правый байт # X соответствует значению массива GF(2^8) значений, # где X_i = alpha^(j_i) X, j = self._chien_search(sigma) # Найдем велинины ошибок с помощью метода Форни # Y в массиве GF(2^8) это значение, соответствующей величины ошибки # с позицией заданной массивом j Y = self._forney(omega, X) # Поставим ошибку и ее положение вместе, в полином ошибок Elist = [] for i in xrange(255): if i in j: Elist.append(Y[j.index(i)]) else: Elist.append(GF256int(0)) E = Polynomial(reversed(Elist)) # Получаем наше кодовое слово c = r - E # Формируем его обратно в строку и возвращаем все последние n-k байт ret = "".join(chr(x) for x in c.coefficients[:-(n - k)]) if nostrip: # Полиномиальные объекты не хранят ведущие нулевые коэффициенты, # поэтому мы на самом деле должны дополнить это до k байт return ret.rjust(k, "\0") else: return ret
def _berlekamp_massey(self, s): """Computes and returns the error locator polynomial (sigma) and the error evaluator polynomial (omega) The parameter s is the syndrome polynomial (syndromes encoded in a generator function) as returned by _syndromes. Don't be confused with the other s = (n-k)/2 Notes: The error polynomial: E(x) = E_0 + E_1 x + ... + E_(n-1) x^(n-1) j_1, j_2, ..., j_s are the error positions. (There are at most s errors) Error location X_i is defined: X_i = α^(j_i) that is, the power of α corresponding to the error location Error magnitude Y_i is defined: E_(j_i) that is, the coefficient in the error polynomial at position j_i Error locator polynomial: sigma(z) = Product( 1 - X_i * z, i=1..s ) roots are the reciprocals of the error locations ( 1/X_1, 1/X_2, ...) Error evaluator polynomial omega(z) not written here """ n = self.n k = self.k # Initialize: sigma = [ Polynomial((GF256int(1),)) ] omega = [ Polynomial((GF256int(1),)) ] tao = [ Polynomial((GF256int(1),)) ] gamma = [ Polynomial((GF256int(0),)) ] D = [ 0 ] B = [ 0 ] # Polynomial constants: ONE = Polynomial(z0=GF256int(1)) ZERO = Polynomial(z0=GF256int(0)) Z = Polynomial(z1=GF256int(1)) # Iteratively compute the polynomials 2s times. The last ones will be # correct for l in xrange(0, n-k): # Goal for each iteration: Compute sigma[l+1] and omega[l+1] such that # (1 + s)*sigma[l] == omega[l] in mod z^(l+1) # For this particular loop iteration, we have sigma[l] and omega[l], # and are computing sigma[l+1] and omega[l+1] # First find Delta, the non-zero coefficient of z^(l+1) in # (1 + s) * sigma[l] # This delta is valid for l (this iteration) only Delta = ( (ONE + s) * sigma[l] ).get_coefficient(l+1) # Make it a polynomial of degree 0 Delta = Polynomial(x0=Delta) # Can now compute sigma[l+1] and omega[l+1] from # sigma[l], omega[l], tao[l], gamma[l], and Delta sigma.append( sigma[l] - Delta * Z * tao[l] ) omega.append( omega[l] - Delta * Z * gamma[l] ) # Now compute the next tao and gamma # There are two ways to do this if Delta == ZERO or 2*D[l] > (l+1): # Rule A D.append( D[l] ) B.append( B[l] ) tao.append( Z * tao[l] ) gamma.append( Z * gamma[l] ) elif Delta != ZERO and 2*D[l] < (l+1): # Rule B D.append( l + 1 - D[l] ) B.append( 1 - B[l] ) tao.append( sigma[l] // Delta ) gamma.append( omega[l] // Delta ) elif 2*D[l] == (l+1): if B[l] == 0: # Rule A (same as above) D.append( D[l] ) B.append( B[l] ) tao.append( Z * tao[l] ) gamma.append( Z * gamma[l] ) else: # Rule B (same as above) D.append( l + 1 - D[l] ) B.append( 1 - B[l] ) tao.append( sigma[l] // Delta ) gamma.append( omega[l] // Delta ) else: raise Exception("Code shouldn't have gotten here") return sigma[-1], omega[-1]
def _berlekamp_massey(self, s): """Вычисляет и возвращает в полином описывающий положение ошибки (sigma) и в полином оценивающий ошибку (omega) Параметр s является синдромом многочлена (синдром кодируется в функциональном генераторе) который возвращает _синдромов. Не путать с другим s = (n-k)/2 Примечания: Полином описывающий ошибку: E(x) = E_0 + E_1 x + ... + E_(n-1) x^(n-1) j_1, j_2, ..., j_s позиция ошибки. (Есть в большинсвте s ошибках) Положение ошибки X_i определяется: X_i = α^(j_i) то есть, мощность α соответствующая положению ошибки Величина ошибки Y_i определяется: E_(j_i) то есть, коэффициент в полиноме описывающем ошибку j_i Полином описывающий положение ошибки: sigma(z) = Product( 1 - X_i * z, i=1..s ) корни обратные местам ошибок ( 1/X_1, 1/X_2, ...) полином оценивающий ошибку omega(z) здесь не описывается """ n = self.n k = self.k # Инициализация: sigma = [Polynomial((GF256int(1), ))] omega = [Polynomial((GF256int(1), ))] tao = [Polynomial((GF256int(1), ))] gamma = [Polynomial((GF256int(0), ))] D = [0] B = [0] # Константы: ONE = Polynomial(z0=GF256int(1)) ZERO = Polynomial(z0=GF256int(0)) Z = Polynomial(z1=GF256int(1)) # Итеративно вычисляем полиномы до 2s раз. # Последний из них будет правильными. for l in xrange(0, n - k): # Цель для каждой итерации: вычисляем sigma[l+1] и omega[l+1] что # (1 + s)*sigma[l] == omega[l] в mod z^(l+1) # Для этой конкретной итерации, мы имеем sigma[l] и omega[l], # и вычисления sigma[l+1] и omega[l+1] # Первым находим Delta, с ненулевыми коэффицентами z^(l+1) в # (1 + s) * sigma[l] # Эта Delta действительна для l (в этой итерации) только Delta = ((ONE + s) * sigma[l]).get_coefficient(l + 1) # Делаем этот полином в степени 0 Delta = Polynomial(x0=Delta) # Теперь можем вычислить sigma[l+1] и omega[l+1] для # sigma[l], omega[l], tao[l], gamma[l], и Delta sigma.append(sigma[l] - Delta * Z * tao[l]) omega.append(omega[l] - Delta * Z * gamma[l]) # Далее вычисляем tao и gamma # Есть два способа сделать это if Delta == ZERO or 2 * D[l] > (l + 1): # Сбособ 1 D.append(D[l]) B.append(B[l]) tao.append(Z * tao[l]) gamma.append(Z * gamma[l]) elif Delta != ZERO and 2 * D[l] < (l + 1): # Способ 2 D.append(l + 1 - D[l]) B.append(1 - B[l]) tao.append(sigma[l] // Delta) gamma.append(omega[l] // Delta) elif 2 * D[l] == (l + 1): if B[l] == 0: # Способ 1 (как указанно выше) D.append(D[l]) B.append(B[l]) tao.append(Z * tao[l]) gamma.append(Z * gamma[l]) else: # Способ 2 (как указанно выше) D.append(l + 1 - D[l]) B.append(1 - B[l]) tao.append(sigma[l] // Delta) gamma.append(omega[l] // Delta) else: raise Exception("Код не должен быть получен здесь") return sigma[-1], omega[-1]
def decode(self, r, nostrip=False): """Given a received string or byte array r, attempts to decode it. If it's a valid codeword, or if there are no more than (n-k)/2 errors, the message is returned. A message always has k bytes, if a message contained less it is left padded with null bytes. When decoded, these leading null bytes are stripped, but that can cause problems if decoding binary data. When nostrip is True, messages returned are always k bytes long. This is useful to make sure no data is lost when decoding binary data. """ n = self.n k = self.k if self.verify(r): # The last n-k bytes are parity if nostrip: return r[:-(n-k)] else: return r[:-(n-k)].lstrip("\0") # Turn r into a polynomial r = Polynomial(GF256int(ord(x)) for x in r) # Compute the syndromes: sz = self._syndromes(r) # Find the error locator polynomial and error evaluator polynomial # using the Berlekamp-Massey algorithm sigma, omega = self._berlekamp_massey(sz) # Now use Chien's procedure to find the error locations # j is an array of integers representing the positions of the errors, 0 # being the rightmost byte # X is a corresponding array of GF(2^8) values where X_i = alpha^(j_i) X, j = self._chien_search(sigma) # And finally, find the error magnitudes with Forney's Formula # Y is an array of GF(2^8) values corresponding to the error magnitude # at the position given by the j array Y = self._forney(omega, X) # Put the error and locations together to form the error polynomial Elist = [] for i in xrange(255): if i in j: Elist.append(Y[j.index(i)]) else: Elist.append(GF256int(0)) E = Polynomial(reversed(Elist)) # And we get our real codeword! c = r - E # Form it back into a string and return all but the last n-k bytes ret = "".join(chr(x) for x in c.coefficients[:-(n-k)]) # :-( if nostrip: # Polynomial objects don't store leading 0 coefficients, so we # actually need to pad this to k bytes return ret.rjust(k, "\0") else: return ret
def test_other_multiply(self): a = GF256int(3) b = GF256int(9) self.assertEqual(a * b, a.multiply(b))