|
import bchlib |
|
import numpy as np |
|
from typing import List, Tuple |
|
import random |
|
from copy import deepcopy |
|
|
|
class RSC(object): |
|
def __init__(self, data_bytes=16, ecc_bytes=4, verbose=False, **kwargs): |
|
from reedsolo import RSCodec |
|
self.rs = RSCodec(ecc_bytes) |
|
if verbose: |
|
print(f'Reed-Solomon ECC len: {ecc_bytes*8} bits') |
|
self.data_len = data_bytes |
|
self.dlen = data_bytes * 8 |
|
self.ecc_len = ecc_bytes * 8 |
|
|
|
def get_total_len(self): |
|
return self.dlen + self.ecc_len |
|
|
|
def encode_text(self, text: List[str]): |
|
return np.array([self._encode_text(t) for t in text]) |
|
|
|
def _encode_text(self, text: str): |
|
text = text + ' ' * (self.dlen // 8 - len(text)) |
|
out = self.rs.encode(text.encode('utf-8')) |
|
out = ''.join(format(x, '08b') for x in out) |
|
out = np.array([int(x) for x in out], dtype=np.float32) |
|
return out |
|
|
|
def decode_text(self, data: np.array): |
|
assert len(data.shape)==2 |
|
return [self._decode_text(d) for d in data] |
|
|
|
def _decode_text(self, data: np.array): |
|
assert len(data.shape)==1 |
|
data = ''.join([str(int(bit)) for bit in data]) |
|
data = bytes(int(data[i: i + 8], 2) for i in range(0, len(data), 8)) |
|
data = bytearray(data) |
|
try: |
|
data = self.rs.decode(data)[0] |
|
data = data.decode('utf-8').strip() |
|
except: |
|
print('Error: Decode failed') |
|
data = get_random_unicode(self.get_total_len()//8) |
|
|
|
return data |
|
|
|
def get_random_unicode(length): |
|
|
|
include_ranges = [ |
|
( 0x0021, 0x0021 ), |
|
( 0x0023, 0x0026 ), |
|
( 0x0028, 0x007E ), |
|
( 0x00A1, 0x00AC ), |
|
( 0x00AE, 0x00FF ), |
|
( 0x0100, 0x017F ), |
|
( 0x0180, 0x024F ), |
|
( 0x2C60, 0x2C7F ), |
|
( 0x16A0, 0x16F0 ), |
|
( 0x0370, 0x0377 ), |
|
( 0x037A, 0x037E ), |
|
( 0x0384, 0x038A ), |
|
( 0x038C, 0x038C ), |
|
] |
|
alphabet = [ |
|
chr(code_point) for current_range in include_ranges |
|
for code_point in range(current_range[0], current_range[1] + 1) |
|
] |
|
return ''.join(random.choice(alphabet) for i in range(length)) |
|
|
|
|
|
class BCH(object): |
|
def __init__(self, BCH_POLYNOMIAL = 137, BCH_BITS = 5, payload_len=100, verbose=True,**kwargs): |
|
self.bch = bchlib.BCH(BCH_POLYNOMIAL, BCH_BITS) |
|
self.payload_len = payload_len |
|
self.data_len = (self.payload_len - self.bch.ecc_bytes*8)//7 |
|
assert self.data_len*7+self.bch.ecc_bytes*8 <= self.bch.n, f'Error! BCH with poly {BCH_POLYNOMIAL} and bits {BCH_BITS} can only encode max {self.bch.n//8} bytes of total payload' |
|
if verbose: |
|
print(f'BCH: POLYNOMIAL={BCH_POLYNOMIAL}, protected bits={BCH_BITS}, payload_len={payload_len} bits, data_len={self.data_len*7} bits ({self.data_len} ascii chars), ecc len={self.bch.ecc_bytes*8} bits') |
|
|
|
def get_total_len(self): |
|
return self.payload_len |
|
|
|
def encode_text(self, text: List[str]): |
|
return np.array([self._encode_text(t) for t in text]) |
|
|
|
def _encode_text(self, text: str): |
|
text = text + ' ' * (self.data_len - len(text)) |
|
|
|
data = encode_text_ascii(text) |
|
ecc = self.bch.encode(data) |
|
packet = data + ecc |
|
packet = ''.join(format(x, '08b') for x in packet) |
|
packet = [int(x) for x in packet] |
|
packet.extend([0]*(self.payload_len - len(packet))) |
|
packet = np.array(packet, dtype=np.float32) |
|
return packet |
|
|
|
def decode_text(self, data: np.array): |
|
assert len(data.shape)==2 |
|
return [self._decode_text(d) for d in data] |
|
|
|
def _decode_text(self, packet: np.array): |
|
assert len(packet.shape)==1 |
|
packet = ''.join([str(int(bit)) for bit in packet]) |
|
packet = packet[:(len(packet)//8*8)] |
|
packet = bytes(int(packet[i: i + 8], 2) for i in range(0, len(packet), 8)) |
|
packet = bytearray(packet) |
|
|
|
data, ecc = packet[:-self.bch.ecc_bytes], packet[-self.bch.ecc_bytes:] |
|
data0 = decode_text_ascii(deepcopy(data)).strip() |
|
bitflips = self.bch.decode_inplace(data, ecc) |
|
if bitflips == -1: |
|
data = data0 |
|
else: |
|
|
|
data = decode_text_ascii(data).strip() |
|
return data |
|
|
|
|
|
def encode_text_ascii(text: str): |
|
|
|
|
|
|
|
text_int7 = [ord(t) & 127 for t in text] |
|
text_bitstr = ''.join(format(t,'07b') for t in text_int7) |
|
if len(text_bitstr) % 8 != 0: |
|
text_bitstr = '0'*(8-len(text_bitstr)%8) + text_bitstr |
|
text_int8 = [int(text_bitstr[i:i+8], 2) for i in range(0, len(text_bitstr), 8)] |
|
return bytearray(text_int8) |
|
|
|
|
|
def decode_text_ascii(text: bytearray): |
|
|
|
|
|
|
|
text_bitstr = ''.join(format(t,'08b') for t in text) |
|
pad = len(text_bitstr) % 7 |
|
if pad != 0: |
|
text_bitstr = text_bitstr[pad:] |
|
text_int7 = [int(text_bitstr[i:i+7], 2) for i in range(0, len(text_bitstr), 7)] |
|
text_bytes = bytes(text_int7) |
|
return text_bytes.decode('utf-8') |
|
|
|
|
|
class ECC(object): |
|
def __init__(self, BCH_POLYNOMIAL = 137, BCH_BITS = 5, **kwargs): |
|
self.bch = bchlib.BCH(BCH_POLYNOMIAL, BCH_BITS) |
|
|
|
def get_total_len(self): |
|
return 100 |
|
|
|
def _encode(self, x): |
|
|
|
|
|
dlen = len(x) |
|
data_str = ''.join(str(x) for x in x.astype(int)) |
|
packet = bytes(int(data_str[i: i + 8], 2) for i in range(0, dlen, 8)) |
|
packet = bytearray(packet) |
|
ecc = self.bch.encode(packet) |
|
packet = packet + ecc |
|
packet = ''.join(format(x, '08b') for x in packet) |
|
packet = [int(x) for x in packet] |
|
packet.extend([0, 0, 0, 0]) |
|
packet = np.array(packet, dtype=np.float32) |
|
return packet |
|
|
|
def _decode(self, x): |
|
|
|
|
|
packet_binary = "".join([str(int(bit)) for bit in x]) |
|
packet = bytes(int(packet_binary[i: i + 8], 2) for i in range(0, len(packet_binary), 8)) |
|
packet = bytearray(packet) |
|
|
|
data, ecc = packet[:-self.bch.ecc_bytes], packet[-self.bch.ecc_bytes:] |
|
bitflips = self.bch.decode_inplace(data, ecc) |
|
if bitflips == -1: |
|
data = np.random.binomial(1, .5, 56) |
|
else: |
|
data = ''.join(format(x, '08b') for x in data) |
|
data = np.array([int(x) for x in data], dtype=np.float32) |
|
return data |
|
|
|
def _generate(self): |
|
dlen = 56 |
|
data= np.random.binomial(1, .5, dlen) |
|
packet = self._encode(data) |
|
return packet, data |
|
|
|
def generate(self, nsamples=1): |
|
|
|
data = [self._generate() for _ in range(nsamples)] |
|
data = (np.array([d[0] for d in data]), np.array([d[1] for d in data])) |
|
return data |
|
|
|
def _to_text(self, data): |
|
|
|
|
|
data = ''.join([str(int(bit)) for bit in data]) |
|
all_bytes = [ data[i: i+8] for i in range(0, len(data), 8) ] |
|
text = ''.join([chr(int(byte, 2)) for byte in all_bytes]) |
|
return text.strip() |
|
|
|
def _to_binary(self, s): |
|
if isinstance(s, str): |
|
out = ''.join([ format(ord(i), "08b") for i in s ]) |
|
elif isinstance(s, bytes): |
|
out = ''.join([ format(i, "08b") for i in s ]) |
|
elif isinstance(s, np.ndarray) and s.dtype is np.dtype(bool): |
|
out = ''.join([chr(int(i)) for i in s]) |
|
elif isinstance(s, int) or isinstance(s, np.uint8): |
|
out = format(s, "08b") |
|
elif isinstance(s, np.ndarray): |
|
out = [ format(i, "08b") for i in s ] |
|
else: |
|
raise TypeError("Type not supported.") |
|
|
|
return np.array([float(i) for i in out], dtype=np.float32) |
|
|
|
def _encode_text(self, s): |
|
s = s + ' '*(7-len(s)) |
|
s = self._to_binary(s) |
|
packet = self._encode(s) |
|
return packet, s |
|
|
|
def encode_text(self, secret_list, return_pre_ecc=False): |
|
"""encode secret with BCH ECC. |
|
Input: secret (list of strings) |
|
Output: secret (np array) with shape (B, 100) type float23, val {0,1}""" |
|
assert np.all(np.array([len(s) for s in secret_list]) <= 7), 'Error! all strings must be less than 7 characters' |
|
secret_list = [self._encode_text(s) for s in secret_list] |
|
ecc = np.array([s[0] for s in secret_list], dtype=np.float32) |
|
if return_pre_ecc: |
|
return ecc, np.array([s[1] for s in secret_list], dtype=np.float32) |
|
return ecc |
|
|
|
def decode_text(self, data): |
|
"""Decode secret with BCH ECC and convert to string. |
|
Input: secret (torch.tensor) with shape (B, 100) type bool |
|
Output: secret (B, 56)""" |
|
data = self.decode(data) |
|
data = [self._to_text(d) for d in data] |
|
return data |
|
|
|
def decode(self, data): |
|
"""Decode secret with BCH ECC and convert to string. |
|
Input: secret (torch.tensor) with shape (B, 100) type bool |
|
Output: secret (B, 56)""" |
|
data = data[:, :96] |
|
data = [self._decode(d) for d in data] |
|
return np.array(data) |
|
|
|
def test_ecc(): |
|
ecc = ECC() |
|
batch_size = 10 |
|
secret_ecc, secret_org = ecc.generate(batch_size) |
|
|
|
secret_pred = secret_ecc.copy() |
|
secret_pred[:,3:6] = 1 - secret_pred[:,3:6] |
|
|
|
secret_pred_org = ecc.decode(secret_pred) |
|
assert np.all(secret_pred_org == secret_org) |
|
|
|
|
|
def test_bch(): |
|
|
|
def check(text, poly, k, l): |
|
bch = BCH(poly, k, l) |
|
|
|
encode = bch.encode_text([text]) |
|
for ind in np.random.choice(l, k): |
|
encode[0, ind] = 1 - encode[0, ind] |
|
text_recon = bch.decode_text(encode)[0] |
|
assert text==text_recon |
|
|
|
check('secrets', 137, 5, 100) |
|
check('some secret', 285, 10, 160) |
|
|
|
if __name__ == '__main__': |
|
test_ecc() |
|
test_bch() |