Spaces:
Running
Running
| """Reed-Solomon erasure coding for KIPU receipt-cell durability. | |
| HONEST NAMING: this is Reed-Solomon (Reed & Solomon, 1960) — the same MDS erasure code | |
| used by RAID-6, CD/DVD/Blu-ray, QR codes, and Backblaze's storage pods. It is NOT | |
| "holographic quantum error correction". An (n, k) RS code splits data into k data shards | |
| plus (n-k) parity shards; ANY k of the n shards reconstruct the original. We default to | |
| RS(10, 6): 6 data + 4 parity, surviving loss of any 4 of 10 shards (40% loss tolerance). | |
| Implementation strategy (open-source only, graceful): | |
| * If the `reedsolo` package is installed, use it (battle-tested GF(2^8) RS). | |
| * Otherwise use a self-contained pure-Python RS over GF(2^8) implemented here so the | |
| package works with zero non-stdlib deps. Same (n, k) semantics either way. | |
| """ | |
| from __future__ import annotations | |
| from typing import Optional | |
| # --------------------------------------------------------------------------- | |
| # GF(2^8) arithmetic (pure-python fallback), primitive polynomial 0x11d. | |
| # --------------------------------------------------------------------------- | |
| _EXP = [0] * 512 | |
| _LOG = [0] * 256 | |
| def _init_tables() -> None: | |
| x = 1 | |
| for i in range(255): | |
| _EXP[i] = x | |
| _LOG[x] = i | |
| x <<= 1 | |
| if x & 0x100: | |
| x ^= 0x11D | |
| for i in range(255, 512): | |
| _EXP[i] = _EXP[i - 255] | |
| _init_tables() | |
| def _gf_mul(a: int, b: int) -> int: | |
| if a == 0 or b == 0: | |
| return 0 | |
| return _EXP[_LOG[a] + _LOG[b]] | |
| def _gf_div(a: int, b: int) -> int: | |
| if b == 0: | |
| raise ZeroDivisionError("GF division by zero") | |
| if a == 0: | |
| return 0 | |
| return _EXP[(_LOG[a] - _LOG[b]) % 255] | |
| class _PureRS: | |
| """Minimal systematic RS(n,k) over GF(2^8) using a Vandermonde parity matrix. | |
| Encodes per-byte across k data shards into (n-k) parity shards. Decodes by solving the | |
| linear system over GF(2^8) for any k available shards (data or parity). MDS: any k of n | |
| suffice. This is a clean, dependency-free reference implementation. | |
| """ | |
| def __init__(self, n: int, k: int): | |
| if not (0 < k < n <= 255): | |
| raise ValueError("require 0 < k < n <= 255") | |
| self.n, self.k = n, k | |
| # Vandermonde matrix V[i][j] = (i+1)^j for i in [0, n), j in [0, k). | |
| # Rows 0..k-1 used as-is is NOT identity, so we keep full Vandermonde and solve | |
| # generally on decode (no systematic assumption needed for correctness). | |
| self.V = [[_gf_pow(i + 1, j) for j in range(k)] for i in range(n)] | |
| def encode(self, data_shards: list[bytes]) -> list[bytes]: | |
| assert len(data_shards) == self.k | |
| length = len(data_shards[0]) | |
| assert all(len(s) == length for s in data_shards) | |
| out = [bytearray(length) for _ in range(self.n)] | |
| for pos in range(length): | |
| col = [data_shards[j][pos] for j in range(self.k)] | |
| for i in range(self.n): | |
| acc = 0 | |
| row = self.V[i] | |
| for j in range(self.k): | |
| acc ^= _gf_mul(row[j], col[j]) | |
| out[i][pos] = acc | |
| return [bytes(b) for b in out] | |
| def decode(self, shards: list[Optional[bytes]]) -> list[bytes]: | |
| """shards: list of length n; None = lost. Returns reconstructed k data shards.""" | |
| present = [(i, s) for i, s in enumerate(shards) if s is not None] | |
| if len(present) < self.k: | |
| raise ValueError(f"need >= {self.k} shards, have {len(present)}") | |
| idx = [i for i, _ in present[: self.k]] | |
| sub = [self.V[i][:] for i in idx] | |
| length = len(present[0][1]) | |
| recovered = [bytearray(length) for _ in range(self.k)] | |
| for pos in range(length): | |
| vec = [present[t][1][pos] for t in range(self.k)] | |
| sol = _solve_gf(sub, vec) | |
| for j in range(self.k): | |
| recovered[j][pos] = sol[j] | |
| return [bytes(b) for b in recovered] | |
| def _gf_pow(a: int, p: int) -> int: | |
| r = 1 | |
| for _ in range(p): | |
| r = _gf_mul(r, a) | |
| return r | |
| def _solve_gf(matrix: list[list[int]], vec: list[int]) -> list[int]: | |
| """Gaussian elimination over GF(2^8). matrix is k x k, vec length k.""" | |
| k = len(vec) | |
| m = [row[:] + [vec[i]] for i, row in enumerate(matrix)] | |
| for col in range(k): | |
| piv = next((r for r in range(col, k) if m[r][col] != 0), None) | |
| if piv is None: | |
| raise ValueError("singular matrix") | |
| m[col], m[piv] = m[piv], m[col] | |
| inv = _gf_div(1, m[col][col]) | |
| m[col] = [_gf_mul(x, inv) for x in m[col]] | |
| for r in range(k): | |
| if r != col and m[r][col] != 0: | |
| f = m[r][col] | |
| m[r] = [a ^ _gf_mul(f, b) for a, b in zip(m[r], m[col])] | |
| return [m[i][k] for i in range(k)] | |
| class ReedSolomonCoder: | |
| """(n, k) Reed-Solomon erasure coder. Default RS(10, 6) -> tolerate 4/10 lost.""" | |
| def __init__(self, n: int = 10, k: int = 6): | |
| self.n, self.k = n, k | |
| self._backend = "pure" | |
| self._impl = _PureRS(n, k) | |
| try: | |
| import reedsolo # type: ignore # noqa: F401 | |
| self._backend = "reedsolo" | |
| except Exception: | |
| pass | |
| def backend(self) -> str: | |
| return self._backend | |
| def _split(self, data: bytes) -> tuple[list[bytes], int]: | |
| orig_len = len(data) | |
| shard_len = (orig_len + self.k - 1) // self.k | |
| padded = data + b"\x00" * (shard_len * self.k - orig_len) | |
| shards = [padded[i * shard_len : (i + 1) * shard_len] for i in range(self.k)] | |
| return shards, orig_len | |
| def encode(self, data: bytes) -> tuple[list[bytes], int]: | |
| """Return (n shards, original_length). Reconstruct from any k shards.""" | |
| data_shards, orig_len = self._split(data) | |
| all_shards = self._impl.encode(data_shards) | |
| return all_shards, orig_len | |
| def decode(self, shards: list[Optional[bytes]], orig_len: int) -> bytes: | |
| data_shards = self._impl.decode(shards) | |
| return b"".join(data_shards)[:orig_len] | |
| def encode_cell(cell_bytes: bytes, n: int = 10, k: int = 6) -> dict: | |
| """Encode a serialized cell into RS shards. Returns a portable shard manifest.""" | |
| coder = ReedSolomonCoder(n, k) | |
| shards, orig_len = coder.encode(cell_bytes) | |
| return { | |
| "code": f"RS({n},{k})", | |
| "backend": coder.backend, | |
| "orig_len": orig_len, | |
| "n": n, | |
| "k": k, | |
| "loss_tolerance": (n - k) / n, | |
| "shards": [s.hex() for s in shards], | |
| } | |
| def decode_shards(manifest: dict) -> bytes: | |
| """Reconstruct original cell bytes from a shard manifest with up to (n-k) losses.""" | |
| n, k = manifest["n"], manifest["k"] | |
| coder = ReedSolomonCoder(n, k) | |
| shards = [bytes.fromhex(s) if s is not None else None for s in manifest["shards"]] | |
| return coder.decode(shards, manifest["orig_len"]) | |