/ erasure_code / share.js
share.js
1 (function() { 2 var me = {}; 3 4 function ZeroDivisionError() { 5 if (!this) return new ZeroDivisionError(); 6 this.message = "division by zero"; 7 this.name = "ZeroDivisionError"; 8 } 9 me.ZeroDivisionError = ZeroDivisionError; 10 11 // per-byte 2^8 Galois field 12 // Note that this imposes a hard limit that the number of extended chunks can 13 // be at most 256 along each dimension 14 function galoistpl(a) { 15 // 2 is not a primitive root, so we have to use 3 as our logarithm base 16 var r = a ^ (a<<1); // a * (x+1) 17 if (r > 0xff) { // overflow? 18 r = r ^ 0x11b; 19 } 20 return r; 21 } 22 23 // Precomputing a multiplication and XOR table for increased speed 24 var glogtable = new Array(256); 25 var gexptable = []; 26 (function() { 27 var v = 1; 28 for (var i = 0; i < 255; i++) { 29 glogtable[v] = i; 30 gexptable.push(v); 31 v = galoistpl(v); 32 } 33 })(); 34 me.glogtable = glogtable; 35 me.gexptable = gexptable; 36 37 function Galois(val) { 38 if (!(this instanceof Galois)) return new Galois(val); 39 if (val instanceof Galois) { 40 this.val = val.val; 41 } else { 42 this.val = val; 43 } 44 if (typeof Object.freeze == 'function') { 45 Object.freeze(this); 46 } 47 } 48 me.Galois = Galois; 49 Galois.prototype.add = Galois.prototype.sub = function(other) { 50 return new Galois(this.val ^ other.val); 51 }; 52 Galois.prototype.mul = function(other) { 53 if (this.val == 0 || other.val == 0) { 54 return new Galois(0); 55 } 56 return new Galois(gexptable[(glogtable[this.val] + 57 glogtable[other.val]) % 255]); 58 }; 59 Galois.prototype.div = function(other) { 60 if (other.val == 0) { 61 throw new ZeroDivisionError(); 62 } 63 if (this.val == 0) { 64 return new Galois(0); 65 } 66 return new Galois(gexptable[(glogtable[this.val] + 255 - 67 glogtable[other.val]) % 255]); 68 }; 69 Galois.prototype.inspect = function() { 70 return ""+this.val; 71 }; 72 73 function powmod(b, e, m) { 74 var r = 1; 75 while (e > 0) { 76 if (e & 1) r = (r * b) % m; 77 b = (b * b) % m; 78 e = e >> 1; 79 } 80 return r; 81 } 82 83 84 // Modular division class 85 function mkModuloClass(n) { 86 if (n <= 2) throw new Error("n must be prime!"); 87 for (var divisor = 2; divisor * divisor <= n; divisor++) { 88 if (n % divisor == 0) { 89 throw new Error("n must be prime!"); 90 } 91 } 92 93 function Mod(val) { 94 if (!(this instanceof Mod)) return new Mod(val); 95 if (val instanceof Mod) { 96 this.val = val.val; 97 } else { 98 this.val = val; 99 } 100 if (typeof Object.freeze == 'function') { 101 Object.freeze(this); 102 } 103 } 104 Mod.modulo = n; 105 Mod.prototype.add = function(other) { 106 return new Mod((this.val + other.val) % n); 107 }; 108 Mod.prototype.sub = function(other) { 109 return new Mod((this.val + n - other.val) % n); 110 }; 111 Mod.prototype.mul = function(other) { 112 return new Mod((this.val * other.val) % n); 113 }; 114 Mod.prototype.div = function(other) { 115 return new Mod((this.val * powmod(other.val, n-2, n)) % n); 116 }; 117 Mod.prototype.inspect = function() { 118 return ""+this.val; 119 }; 120 121 return Mod; 122 } 123 me.mkModuloClass = mkModuloClass; 124 125 // Evaluates a polynomial in little-endian form, eg. x^2 + 3x + 2 = [2, 3, 1] 126 // (normally I hate little-endian, but in this case dealing with polynomials 127 // it's justified, since you get the nice property that p[n] is the nth degree 128 // term of p) at coordinate x, eg. eval_poly_at([2, 3, 1], 5) = 42 if you are 129 // using float as your arithmetic 130 function eval_poly_at(p, x) { 131 var arithmetic = p[0].constructor; 132 var y = new arithmetic(0); 133 var x_to_the_i = new arithmetic(1); 134 for (var i = 0; i < p.length; i++) { 135 y = y.add(x_to_the_i.mul(p[i])) 136 x_to_the_i = x_to_the_i.mul(x); 137 } 138 return y; 139 } 140 me.eval_poly_at = eval_poly_at; 141 142 // Given p+1 y values and x values with no errors, recovers the original 143 // p+1 degree polynomial. For example, 144 // lagrange_interp([51.0, 59.0, 66.0], [1, 3, 4]) = [50.0, 0, 1.0] 145 // if you are using float as your arithmetic 146 function lagrange_interp(pieces, xs) { 147 var arithmetic = pieces[0].constructor; 148 var zero = new arithmetic(0); 149 var one = new arithmetic(1); 150 // Generate master numerator polynomial 151 var root = [one]; 152 var i, j; 153 for (i = 0; i < xs.length; i++) { 154 root.unshift(zero); 155 for (j = 0; j < root.length - 1; j++) { 156 root[j] = root[j].sub(root[j+1].mul(xs[i])); 157 } 158 } 159 // Generate per-value numerator polynomials by dividing the master 160 // polynomial back by each x coordinate 161 var nums = []; 162 for (i = 0; i < xs.length; i++) { 163 var output = []; 164 var last = one; 165 for (j = 2; j < root.length+1; j++) { 166 output.unshift(last); 167 if (j != root.length) { 168 last = root[root.length-j].add(last.mul(xs[i])); 169 } 170 } 171 nums.push(output); 172 } 173 // Generate denominators by evaluating numerator polys at their x 174 var denoms = []; 175 for (i = 0; i < xs.length; i++) { 176 var denom = zero; 177 var x_to_the_j = one; 178 for (j = 0; j < nums[i].length; j++) { 179 denom = denom.add(x_to_the_j.mul(nums[i][j])); 180 x_to_the_j = x_to_the_j.mul(xs[i]); 181 } 182 denoms.push(denom); 183 } 184 // Generate output polynomial 185 var b = []; 186 for (i = 0; i < pieces.length; i++) { 187 b[i] = zero; 188 } 189 for (i = 0; i < xs.length; i++) { 190 var yslice = pieces[i].div(denoms[i]); 191 for (j = 0; j < pieces.length; j++) { 192 b[j] = b[j].add(nums[i][j].mul(yslice)); 193 } 194 } 195 return b; 196 } 197 me.lagrange_interp = lagrange_interp; 198 199 // Compresses two linear equations of length n into one 200 // equation of length n-1 201 // Format: 202 // 3x + 4y = 80 (ie. 3x + 4y - 80 = 0) -> a = [3,4,-80] 203 // 5x + 2y = 70 (ie. 5x + 2y - 70 = 0) -> b = [5,2,-70] 204 function elim(a, b) { 205 var c = []; 206 for (var i = 1; i < a.length; i++) { 207 c[i-1] = a[i].mul(b[0]).sub(b[i].mul(a[0])); 208 } 209 return c; 210 } 211 212 // Linear equation solver 213 // Format: 214 // 3x + 4y = 80, y = 5 (ie. 3x + 4y - 80z = 0, y = 5, z = 1) 215 // -> coeffs = [3,4,-80], vals = [5,1] 216 function evaluate(coeffs, vals) { 217 var arithmetic = coeffs[0].constructor; 218 var tot = new arithmetic(0); 219 for (var i = 0; i < vals.length; i++) { 220 tot = tot.sub(coeffs[i+1].mul(vals[i])); 221 } 222 if (coeffs[0].val == 0) { 223 throw new ZeroDivisionError(); 224 } 225 return tot.div(coeffs[0]); 226 } 227 228 // Linear equation system solver 229 // Format: 230 // ax + by + c = 0, dx + ey + f = 0 231 // -> [[a, b, c], [d, e, f]] 232 // eg. 233 // [[3.0, 5.0, -13.0], [9.0, 1.0, -11.0]] -> [1.0, 2.0] 234 function sys_solve(eqs) { 235 var arithmetic = eqs[0][0].constructor; 236 var one = new arithmetic(1); 237 var back_eqs = [eqs[0]]; 238 var i; 239 while (eqs.length > 1) { 240 var neweqs = []; 241 for (i = 0; i < eqs.length - 1; i++) { 242 neweqs.push(elim(eqs[i], eqs[i+1])); 243 } 244 eqs = neweqs; 245 i = 0; 246 while (i < eqs.length - 1 && eqs[i][0].val == 0) { 247 i++; 248 } 249 back_eqs.unshift(eqs[i]); 250 } 251 var kvals = [one]; 252 for (i = 0; i < back_eqs.length; i++) { 253 kvals.unshift(evaluate(back_eqs[i], kvals)); 254 } 255 return kvals.slice(0, -1); 256 } 257 me.sys_solve = sys_solve; 258 259 function polydiv(Q, E) { 260 var qpoly = Q.slice(); 261 var epoly = E.slice(); 262 var div = []; 263 while (qpoly.length >= epoly.length) { 264 div.unshift(qpoly[qpoly.length-1].div(epoly[epoly.length-1])); 265 for (var i = 2; i < epoly.length + 1; i++) { 266 qpoly[qpoly.length-i] = 267 qpoly[qpoly.length-i].sub(div[0].mul(epoly[epoly.length-i])); 268 } 269 qpoly.pop(); 270 } 271 return div; 272 } 273 me.polydiv = polydiv; 274 275 // Given a set of y coordinates and x coordinates, and the degree of the 276 // original polynomial, determines the original polynomial even if some of 277 // the y coordinates are wrong. If m is the minimal number of pieces (ie. 278 // degree + 1), t is the total number of pieces provided, then the algo can 279 // handle up to (t-m)/2 errors. See: 280 // http://en.wikipedia.org/wiki/Berlekamp%E2%80%93Welch_algorithm#Example 281 // (just skip to my example, the rest of the article sucks imo) 282 function berlekamp_welch_attempt(pieces, xs, master_degree) { 283 var error_locator_degree = Math.floor((pieces.length - master_degree - 1) / 2); 284 var arithmetic = pieces[0].constructor; 285 var zero = new arithmetic(0); 286 var one = new arithmetic(1); 287 // Set up the equations for y[i]E(x[i]) = Q(x[i]) 288 // degree(E) = error_locator_degree 289 // degree(Q) = master_degree + error_locator_degree - 1 290 var eqs = []; 291 var i,j; 292 for (i = 0; i < 2 * error_locator_degree + master_degree + 1; i++) { 293 eqs.push([]); 294 } 295 for (i = 0; i < 2 * error_locator_degree + master_degree + 1; i++) { 296 var neg_x_to_the_j = zero.sub(one); 297 for (j = 0; j < error_locator_degree + master_degree + 1; j++) { 298 eqs[i].push(neg_x_to_the_j); 299 neg_x_to_the_j = neg_x_to_the_j.mul(xs[i]); 300 } 301 var x_to_the_j = one; 302 for (j = 0; j < error_locator_degree + 1; j++) { 303 eqs[i].push(x_to_the_j.mul(pieces[i])); 304 x_to_the_j = x_to_the_j.mul(xs[i]); 305 } 306 } 307 // Solve 'em 308 // Assume the top error polynomial term to be one 309 var errors = error_locator_degree; 310 var ones = 1; 311 var polys; 312 while (errors >= 0) { 313 try { 314 polys = sys_solve(eqs); 315 for (i = 0; i < ones; i++) polys.push(one); 316 break; 317 } catch (e) { 318 if (e instanceof ZeroDivisionError) { 319 eqs.pop(); 320 for (i = 0; i < eqs.length; i++) { 321 var eq = eqs[i]; 322 eq[eq.length-2] = eq[eq.length-2].add(eq[eq.length-1]); 323 eq.pop(); 324 } 325 errors--; 326 ones++; 327 } else { 328 throw e; 329 } 330 } 331 } 332 if (errors < 0) { 333 throw new Error("Not enough data!"); 334 } 335 // Divide the polynomials 336 var qpoly = polys.slice(0, error_locator_degree + master_degree + 1); 337 var epoly = polys.slice(error_locator_degree + master_degree + 1); 338 var div = polydiv(qpoly, epoly); 339 // Check 340 var corrects = 0; 341 for (i = 0; i < xs.length; i++) { 342 if (eval_poly_at(div, xs[i]).val == pieces[i].val) { 343 corrects++; 344 } 345 } 346 if (corrects < master_degree + errors) { 347 throw new Error("Answer doesn't match (too many errors)!"); 348 } 349 return div; 350 } 351 me.berlekamp_welch_attempt = berlekamp_welch_attempt; 352 353 // Extends a list of integers in [0 ... 255] (if using Galois arithmetic) by 354 // adding n redundant error-correction values 355 function extend(data, n, arithmetic) { 356 arithmetic = arithmetic || Galois; 357 function mk(x) { return new arithmetic(x); } 358 var data2 = data.map(mk); 359 var data3 = data.slice(); 360 var xs = []; 361 var i; 362 for (i = 0; i < data.length; i++) { 363 xs.push(new arithmetic(i)); 364 } 365 var poly = berlekamp_welch_attempt(data2, xs, data.length - 1); 366 for (i = 0; i < n; i++) { 367 data3.push(eval_poly_at(poly, new arithmetic(data.length + i)).val); 368 } 369 return data3; 370 } 371 me.extend = extend; 372 373 // Repairs a list of integers in [0 ... 255]. Some integers can be 374 // erroneous, and you can put null (or undefined) in place of an integer if 375 // you know that a certain value is defective or missing. Uses the 376 // Berlekamp-Welch algorithm to do error-correction 377 function repair(data, datasize, arithmetic) { 378 arithmetic = arithmetic || Galois; 379 var vs = []; 380 var xs = []; 381 var i; 382 for (var i = 0; i < data.length; i++) { 383 if (data[i] != null) { 384 vs.push(new arithmetic(data[i])); 385 xs.push(new arithmetic(i)); 386 } 387 } 388 var poly = berlekamp_welch_attempt(vs, xs, datasize - 1); 389 var result = []; 390 for (i = 0; i < data.length; i++) { 391 result.push(eval_poly_at(poly, new arithmetic(i)).val); 392 } 393 return result; 394 } 395 me.repair = repair; 396 397 function transpose(xs) { 398 var ys = []; 399 for (var i = 0; i < xs[0].length; i++) { 400 var y = []; 401 for (var j = 0; j < xs.length; j++) { 402 y.push(xs[j][i]); 403 } 404 ys.push(y); 405 } 406 return ys; 407 } 408 409 // Extends a list of bytearrays 410 // eg. extend_chunks([map(ord, 'hello'), map(ord, 'world')], 2) 411 // n is the number of redundant error-correction chunks to add 412 function extend_chunks(data, n, arithmetic) { 413 arithmetic = arithmetic || Galois; 414 var o = []; 415 for (var i = 0; i < data[0].length; i++) { 416 o.push(extend(data.map(function(x) { return x[i]; }), n, arithmetic)); 417 } 418 return transpose(o); 419 } 420 me.extend_chunks = extend_chunks; 421 422 // Repairs a list of bytearrays. Use null in place of a missing array. 423 // Individual arrays can contain some missing or erroneous data. 424 function repair_chunks(data, datasize, arithmetic) { 425 arithmetic = arithmetic || Galois; 426 var first_nonzero = 0; 427 while (data[first_nonzero] == null) { 428 first_nonzero++; 429 } 430 var i; 431 for (i = 0; i < data.length; i++) { 432 if (data[i] == null) { 433 data[i] = new Array(data[first_nonzero].length); 434 } 435 } 436 var o = []; 437 for (i = 0; i < data[0].length; i++) { 438 o.push(repair(data.map(function(x) { return x[i]; }), datasize, arithmetic)); 439 } 440 return transpose(o); 441 } 442 me.repair_chunks = repair_chunks; 443 444 // Extends either a bytearray or a list of bytearrays or a list of lists... 445 // Used in the cubify method to expand a cube in all dimensions 446 function deep_extend_chunks(data, n, arithmetic) { 447 arithmetic = arithmetic || Galois; 448 if (!(data[0] instanceof Array)) { 449 return extend(data, n, arithmetic) 450 } else { 451 var o = []; 452 for (var i = 0; i < data[0].length; i++) { 453 o.push(deep_extend_chunks( 454 data.map(function(x) { return x[i]; }), n, arithmetic)); 455 } 456 return transpose(o); 457 } 458 } 459 me.deep_extend_chunks = deep_extend_chunks; 460 461 function isObject(o) { 462 return typeof o == 'object' || typeof o == 'function'; 463 } 464 if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) { 465 define(function() { 466 return me; 467 }); 468 } else { 469 done = 0 470 try { 471 if (isObject(module)) { module.exports = me; } 472 else (isObject(window) ? window : this).Erasure = me; 473 } 474 catch(e) { 475 (isObject(window) ? window : this).Erasure = me; 476 } 477 } 478 }.call(this));