Mii QR Code scanner

using sjcl + qr-scanner, although it is a fork that allows binary data. beware it uses a private method of sjcl due to difficulties I experienced with the library.

by arian_

HTML

<h2>Scan QR Code</h2>
<button id="start-camera">From camera</button>
<button id="stop-button">Cancel</button>
<div>
    <label for="cam-list">Select camera:</label>
    <select id="cam-list">
        <option value="environment" selected>Back Camera (default)</option>
        <option value="user">User/Front Facing Camera</option>
        <option class="device-camera" disabled>(Open the camera for more options)</option>
    </select>
</div>
<input type="file" id="file-input" accept="image/*">
<video id="qr-video" style="max-width: 100%"></video>
<ul id="result-list"></ul>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/sjcl.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/core/codecBytes.js"></script>

<!--<script src="https://cdn.jsdelivr.net/npm/[email protected]/qr-scanner.umd.min.js"></script>-->
<!-- custom FORK!!!!!! of qr-scanner that supports binary data
https://github.com/nimiq/qr-scanner/pull/191
-->
<script src="https://cdn.jsdelivr.net/gh/criteo-forks/qr-scanner/qr-scanner.umd.min.js"></script>

CSS

pre {
  white-space: pre-wrap;
  word-break: break-all;
}

JavaScript

// FOR LOGGING
function buf2hex(buffer) { // buffer is an ArrayBuffer
  return [...new Uint8Array(buffer)]
    .map(x => x.toString(16).padStart(2, '0'))
    .join('');
}

function decryptAesCcm(encryptedData) {
	// if the length is smaller than the standard mii qr code size
	if(encryptedData.length < 112) {
  	throw new Error('Mii QR codes should be 112 or more bytes long, yours is ' + encryptedData.length);
  }
  // Extract nonce and encrypted content
  const nonce = encryptedData.slice(0, 8);
  const encryptedContent = encryptedData.slice(8);

  //const key = sjcl.codec.hex.toBits('59FC817E6446EA6190347B20E9BDCE52');
  // hardcoding the key, sjcl formats keys in these huge 32 bit ints
  const cipher = new sjcl.cipher.aes([1509720446, 1682369121, -1875608800, -373436846]);

  // Convert nonce and encrypted content to bits, adjusting the nonce to full size
  const encryptedBits = sjcl.codec.bytes.toBits(Array.from(encryptedContent));
  let nonceBits = sjcl.codec.bytes.toBits([...nonce, 0, 0, 0, 0]);

  // Isolate the actual ciphertext from the tag and adjust IV
  const tlen = 128; // Tag length in bits
  let out = sjcl.bitArray.clamp(encryptedBits,
    // remove tag from out, tag length = 128
    sjcl.bitArray.bitLength(encryptedBits) - tlen);

  // regex to find the _ctrMode function: 6 arguments and calls "bitSlice"
  const ctrModeFuncRegex = /\([^)]*,[^)]*,[^)]*,[^)]*,[^)]*,[^)]*\)\s*.*?bitSlice/;

  // jsdelivr (1.0.8 sjcl.min.js) minifies this function name to "C"
  const ctrDecrypt = sjcl.mode.ccm._ctrMode || sjcl.mode.ccm.C ||
    // attempt to find the private _ctrMode func using our regex
    Object.entries(sjcl.mode.ccm).find(
      // match string representation of the function
      ([_, fn]) => fn.toString().match(ctrModeFuncRegex)
    )[1];
  // may throw IndexError??
  if (!ctrDecrypt) throw new Error('WE CANNOT FIND HIDDEN sjcl.mode.ccm._ctrMode DECRYPT FUNCTION!!!!!!');
  const decryptedBits = ctrDecrypt(cipher, out, nonceBits, [], tlen, 3) //...