docs: function(encrypt, prf, data, adata, iv, tlen) {
      var H, J0, S0, enc, i, ctr, tag, last, l, bl, abl, ivbl, w=sjcl.bitArray;
  
      // Calculate data lengths
      l = data.length;
      bl = w.bitLength(data);
      abl = w.bitLength(adata);
      ivbl = w.bitLength(iv);
  
      // Calculate the parameters
      H = prf.encrypt([0,0,0,0]);
      if (ivbl === 96) {
        J0 = iv.slice(0);
        J0 = w.concat(J0, [1]);
      } else {
        J0 = sjcl.mode.gcm._ghash(H, [0,0,0,0], iv);
        J0 = sjcl.mode.gcm._ghash(H, J0, [0,0,Math.floor(ivbl/0x100000000),ivbl&0xffffffff]);
      }
      S0 = sjcl.mode.gcm._ghash(H, [0,0,0,0], adata);
  
      // Initialize ctr and tag
      ctr = J0.slice(0);
      tag = S0.slice(0);
  
      // If decrypting, calculate hash
      if (!encrypt) {
        tag = sjcl.mode.gcm._ghash(H, S0, data);
      }
  
      // Encrypt all the data
      for (i=0; i<l; i+=4) {
         ctr[3]++;
         enc = prf.encrypt(ctr);
         data[i]   ^= enc[0];
         data[i+1] ^= enc[1];
         data[i+2] ^= enc[2];
         data[i+3] ^= enc[3];
      }
      data = w.clamp(data, bl);
  
      // If encrypting, calculate hash
      if (encrypt) {
        tag = sjcl.mode.gcm._ghash(H, S0, data);
      }
  
      // Calculate last block from bit lengths, ugly because bitwise operations are 32-bit
      last = [
        Math.floor(abl/0x100000000), abl&0xffffffff,
        Math.floor(bl/0x100000000), bl&0xffffffff
      ];
  
      // Calculate the final tag block
      tag = sjcl.mode.gcm._ghash(H, tag, last);
      enc = prf.encrypt(J0);
      tag[0] ^= enc[0];
      tag[1] ^= enc[1];
      tag[2] ^= enc[2];
      tag[3] ^= enc[3];
  
      return { tag:w.bitSlice(tag, 0, tlen), data:data };
    }