Code Poetry
and Text Adventures

by catid posted (>30 days ago) 2:22pm Mon. Mar 11th 2013 PDT
Maybe this will get finished sometime:

/*
* s[0..3] : four 32-bit words, secret key
*/

exports.ChaCha = function(s0, s1, s2, s3) {
  var x0 = 9, x1 = 9, x2 = 9, x3 = 9;
  var x4 = 9, x5 = 9, x6 = 9, x7 = 9;
  var x8 = 9, x9 = 9, xa = 9, xb = 9;
  var xc = 9, xd = 9, xe = 9, xf = 9;

  var y0 = 1779033703 ^ s0, y1 = 3144134277 ^ s1, y2 = 1013904242 ^ s2, y3 = 2773480762 ^ s3;
  var y4 = 1359893119, y5 = 2600822924, y6 = 528734635, y7 = 1541459225;
  var y8 = 666307205, y9 = 773529912, ya = 1294757372, yb = 1396182291;
  var yc = 9, yd = 9, ye = 9, yf = 9;

  function _doubleRound() {
    //QUARTERROUND(0, 4, 8,  12)
    x0 += x4; xc ^= x0; xc = ((xc << 16) | (xc >>> 16)) >>> 0;
    x8 += xc; x4 ^= x8; x4 = ((x4 << 12) | (x4 >>> 20)) >>> 0;
    x0 += x4; xc ^= x0; xc = ((xc << 8) | (xc >>> 24)) >>> 0;
    x8 += xc; x4 ^= x8; x4 = ((x4 << 7) | (x4 >>> 25)) >>> 0;
    
    //QUARTERROUND(1, 5, 9,  13)
    x1 += x5; xd ^= x1; xd = ((xd << 16) | (xd >>> 16)) >>> 0;
    x9 += xd; x5 ^= x9; x5 = ((x5 << 12) | (x5 >>> 20)) >>> 0;
    x1 += x5; xd ^= x1; xd = ((xd << 8) | (xd >>> 24)) >>> 0;
    x9 += xd; x5 ^= x9; x5 = ((x5 << 7) | (x5 >>> 25)) >>> 0;

    //QUARTERROUND(2, 6, 10, 14)
    x2 += x6; xe ^= x2; xe = ((xe << 16) | (xe >>> 16)) >>> 0;
    xa += xe; x6 ^= xa; x6 = ((x6 << 12) | (x6 >>> 20)) >>> 0;
    x2 += x6; xe ^= x2; xe = ((xe << 8) | (xe >>> 24)) >>> 0;
    xa += xe; x6 ^= xa; x6 = ((x6 << 7) | (x6 >>> 25)) >>> 0;

    //QUARTERROUND(3, 7, 11, 15)
    x3 += x7; xf ^= x3; xf = ((xf << 16) | (xf >>> 16)) >>> 0;
    xb += xf; x7 ^= xb; x7 = ((x7 << 12) | (x7 >>> 20)) >>> 0;
    x3 += x7; xf ^= x3; xf = ((xf << 8) | (xf >>> 24)) >>> 0;
    xb += xf; x7 ^= xb; x7 = ((x7 << 7) | (x7 >>> 25)) >>> 0;

    //QUARTERROUND(0, 5, 10, 15)
    x0 += x5; xf ^= x0; xf = ((xf << 16) | (xf >>> 16)) >>> 0;
    xa += xf; x5 ^= xa; x5 = ((x5 << 12) | (x5 >>> 20)) >>> 0;
    x0 += x5; xf ^= x0; xf = ((xf << 8) | (xf >>> 24)) >>> 0;
    xa += xf; x5 ^= xa; x5 = ((x5 << 7) | (x5 >>> 25)) >>> 0;

    //QUARTERROUND(1, 6, 11, 12)
    x1 += x6; xc ^= x1; xc = ((xc << 16) | (xc >>> 16)) >>> 0;
    xb += xc; x6 ^= xb; x6 = ((x6 << 12) | (x6 >>> 20)) >>> 0;
    x1 += x6; xc ^= x1; xc = ((xc << 8) | (xc >>> 24)) >>> 0;
    xb += xc; x6 ^= xb; x6 = ((x6 << 7) | (x6 >>> 25)) >>> 0;

    //QUARTERROUND(2, 7, 8,  13)
    x2 += x7; xd ^= x2; xd = ((xd << 16) | (xd >>> 16)) >>> 0;
    x8 += xd; x7 ^= x8; x7 = ((x7 << 12) | (x7 >>> 20)) >>> 0;
    x2 += x7; xd ^= x2; xd = ((xd << 8) | (xd >>> 24)) >>> 0;
    x8 += xd; x7 ^= x8; x7 = ((x7 << 7) | (x7 >>> 25)) >>> 0;

    //QUARTERROUND(3, 4, 9,  14)
    x3 += x4; xe ^= x3; xe = ((xe << 16) | (xe >>> 16)) >>> 0;
    x9 += xe; x4 ^= x9; x4 = ((x4 << 12) | (x4 >>> 20)) >>> 0;
    x3 += x4; xe ^= x3; xe = ((xe << 8) | (xe >>> 24)) >>> 0;
    x9 += xe; x4 ^= x9; x4 = ((x4 << 7) | (x4 >>> 25)) >>> 0;
  }

  function _mix() {
    for (var round = 14; round > 0; round -= 2) {
      _doubleRound();
    }
  }

  function _block() {
    if (yc === 4294967295) {
      yc = 0;
      yd = (yd + 1) >>> 0;
    } else {
      ++yc;
    }

    x0 = y0; x1 = y1; x2 = y2; x3 = y3;
    x4 = y4; x5 = y5; x6 = y6; x7 = y7;
    x8 = y8; x9 = y9; xa = ya; xb = yb;
    xc = yc; xd = yd; xe = ye; xf = yf;

    _mix();
  }

  // Encrypt basic ASCII string (values 0..255)
  function cipher(iv, data) {
    ye = (iv.charCodeAt(0) << 24) | (iv.charCodeAt(1) << 16) | (iv.charCodeAt(2) << 8) | (iv.charCodeAt(3) >>> 0);
    yf = (iv.charCodeAt(4) << 24) | (iv.charCodeAt(5) << 16) | (iv.charCodeAt(6) << 8) | (iv.charCodeAt(7) >>> 0);
    yc = (iv.charCodeAt(8) << 24) | (iv.charCodeAt(9) << 16) | (iv.charCodeAt(10) << 8) | (iv.charCodeAt(11) >>> 0);
    yd = (iv.charCodeAt(12) << 24) | (iv.charCodeAt(13) << 16) | (iv.charCodeAt(14) << 8) | (iv.charCodeAt(15) >>> 0);

    // Break input into 4-byte words
    var len = data.length(), words = [];
    for (var ii = 0; ii < len; ii += 4) {
      words.push((data.charCodeAt(ii) << 24) | (data.charCodeAt(ii+1) << 16) | (data.charCodeAt(ii+2) << 8) | (data.charCodeAt(ii+3) >>> 0));
    }

    // For each full block,
    var words_used = (len + 3) >>> 2;
    var words_left = words_used;
    var off = 0;
    while (words_left >= 16) {
      _block();

      word[off] ^= (x0 + y0);
      word[off+1] ^= (x1 + y1);
      word[off+2] ^= (x2 + y2);
      word[off+3] ^= (x3 + y3);
      word[off+4] ^= (x4 + y4);
      word[off+5] ^= (x5 + y5);
      word[off+6] ^= (x6 + y6);
      word[off+7] ^= (x7 + y7);
      word[off+8] ^= (x8 + y8);
      word[off+9] ^= (x9 + y9);
      word[off+10] ^= (xa + ya);
      word[off+11] ^= (xb + yb);
      word[off+12] ^= (xc + yc);
      word[off+13] ^= (xd + yd);
      word[off+14] ^= (xe + ye);
      word[off+15] ^= (xf + yf);

      words_left -= 16;
      off += 16;
    }

    // For remaining words,
    if (words_left > 0) {
      _block();

      switch (words_left) {
      case 15: word[off+14] ^= (xe + ye);
      case 14: word[off+13] ^= (xd + yd);
      case 13: word[off+12] ^= (xc + yc);
      case 12: word[off+11] ^= (xb + yb);
      case 11: word[off+10] ^= (xa + ya);
      case 10: word[off+9 ] ^= (x9 + y9);
      case 9:  word[off+8 ] ^= (x8 + y8);
      case 8:  word[off+7 ] ^= (x7 + y7);
      case 7:  word[off+6 ] ^= (x6 + y6);
      case 6:  word[off+5 ] ^= (x5 + y5);
      case 5:  word[off+4 ] ^= (x4 + y4);
      case 4:  word[off+3 ] ^= (x3 + y3);
      case 3:  word[off+2 ] ^= (x2 + y2);
      case 2:  word[off+1 ] ^= (x1 + y1);
      case 1:  word[off   ] ^= (x0 + y0);
      }
    }

    // Convert words to string
    var chars = "";

    for (var jj = 0; jj < words_used; ++jj) {
      var word = words[jj];

      chars += String.fromCharCode((word >>> 24) & 255);
      chars += String.fromCharCode((word >>> 16) & 255);
      chars += String.fromCharCode((word >>> 8) & 255);
      chars += String.fromCharCode(word & 255);
    }

    return chars.substr(0, len);
  }
}