step 1: calling local:qwen3.6:35b-a3b
tool list <- {"path":"."}
tool list ok: .git/
.github/
.gitignore
.husky/
.npmignore
.nyc_output/
.prettierignore
CHANGELOG.md
CONTRIBUTING.md
LICENSE
README.md
SECURITY.md
bower.json
coverage/
dist/
eslint.config.js
karma.conf.js
node_modules/
package-lock.json
package.json
rollup.config.mjs
scripts/
src/
test/
tsconfig.json
[evidence record sha256:25d52b93ec844e947d15405d89dcba0ef0df806ec5a55914bcefd2cef22df8b5 kind tool-call:list]
step 2: calling local:qwen3.6:35b-a3b
tool read <- {"path":"package.json"}
tool read ok: {
  "name": "jssha",
  "version": "3.3.2",
  "description": "jsSHA implements the complete Secure Hash Standard (SHA) family (SHA-1, SHA-224/256/384/512, SHA3-224/256/384/512, SHAKE128/256, cSHAKE128/256, and KMAC128/256) with HMAC",
  "main": "./dist/sha.js",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/sha.d.mts",
        "default": "./dist/sha.mjs"
      },
      "require": {
        "types": "./dist/sha.d.cts",
        "default": "./dist/sha.js"
      }
    },
    "./sha1": {
      "import": {
        "types": "./dist/sha1.d.mts",
        "default": "./dist/sha1.mjs"
      },
      "require": {
        "types": "./dist/sha1.d.cts",
        "default": "./dist/sha1.js"
      }
    },
    "./dist/sha1": {
      "import": {
        "types": "./dist/sha1.d.mts",
        "default": "./dist/sha1.mjs"
      },
      "require": {
        "types": "./dist/sha1.d.cts",
        "default": "./dist/sha1.js"
      }
    },
    "./sha256": {
      "import": {
        "types": "./dist/sha256.d.mts",
        "default": "./dist/sha256.mjs"
      },
      "require": {
        "types": "./dist/sha256.d.cts",
        "default": "./dist/sha256.js"
      }
    },
    "./dist/sha256": {
      "import": {
        "types": "./dist/sha256.d.mts",
        "default": "./dist/sha256.mjs"
      },
      "require": {
        "types": "./dist/sha256.d.cts",
        "default": "./dist/sha256.js"
      }
    },
    "./sha512": {
      "import": {
        "types": "./dist/sha512.d.mts",
        "default": "./dist/sha512.mjs"
      },
      "require": {
        "types": "./dist/sha512.d.cts",
        "default": "./dist/sha512.js"
      }
    },
    "./dist/sha512": {
      "import": {
        "types": "./dist/sha512.d.mts",
        "default": "./dist/sha512.mjs"
      },
      "require": {
        "types": "./dist/sha512.d.cts",
        "default": "./dist/sha512.js"
      }
    },
    "./sha3": {
      "import": {
        "types": "./dist/sha3.d.mts",
        "default": "./dist/sha3.mjs"
      },
      "require": {
        "types": "./dist/sha3.d.cts",
        "default": "./dist/sha3.js"
      }
    },
    "./dist/sha3": {
      "import": {
        "types": "./dist/sha3.d.mts",
        "default": "./dist/sha3.mjs"
      },
      "require": {
        "types": "./dist/sha3.d.cts",
        "default": "./dist/sha3.js"
      }
    },
    "./package.json": "./package.json"
  },
  "module": "./dist/sha.mjs",
  "types": "./dist/sha.d.ts",
  "repository": {
    "type": "git",
    "url": "https://github.com/Caligatio/jsSHA.git"
  },
  "keywords": [
    "SHA-1",
    "SHA-224",
    "SHA3-224",
    "SHA-256",
    "SHA3-256",
    "SHA-384",
    "SHA3-384",
    "SHA-512",
    "SHA3-512",
    "SHAKE-128",
    "SHAKE-256",
    "SHAKE128",
    "SHAKE256",
    "cSHAKE128",
    "cSHAKE256",
    "KMAC128",
    "KMAC256",
    "SHA1",
    "SHA224",
    "SHA256",
    "SHA384",
    "SHA512",
    "SHA-2",
    "SHA2",
    "SHA-3",
    "SHA3",
    "SHAKE",
    "cSHAKE",
    "cSHAKE-128",
    "cSHAKE-256",
    "HMAC",
    "KMAC",
    "KMAC-128",
    "KMAC-256",
    "hash",
    "cryptography"
  ],
  "license": "BSD-3-Clause",
  "author": "Brian Turek <brian.turek@gmail.com>",
  "bugs": {
    "url": "https://github.com/Caligatio/jsSHA/issues"
  },
  "engines": {
    "node": "*"
  },
  "homepage": "https://github.com/Caligatio/jsSHA",
  "devDependencies": {
    "@eslint/js": "^10.0.1",
    "@rollup/plugin-terser": "^1.0.0",
    "@rollup/plugin-typescript": "^12.3.0",
    "@types/chai": "^4",
    "@types/mocha": "^10.0.10",
    "@types/rewire": "^2.5.30",
    "@types/sinon": "^22.0.0",
    "chai": "^4",
    "eslint": "^10.5.0",
    "husky": "^9.1.7",
    "karma": "^6.4.4",
    "karma-chai": "^0.1.0",
    "karma-chrome-launcher": "^3.2.0",
    "karma-firefox-launcher": "^2.1.3",
    "karma-mocha": "^2.0.1",
    "lint-staged": "^17.0.8",
    "mocha": "^11.7.6",
    "nyc": "^18.0.0",
    "prettier": "^3.8.4",
    "rewire": "^9.0.1",
    "rollup": "^4.62.2",
    "rollup-plugin-dts": "^6.4.1",
    "sinon": "^22.0.0",
    "ts-node": "^10.9.2",
    "tslib": "^2.8.1",
    "typescript": "<5.5",
    "typescript-eslint": "^8.62.0"
  },
  "scripts": {
    "build": "rollup -c && node scripts/generate-dual-types.mjs",
    "test": "nyc --reporter=html --reporter=text mocha test/src/*.ts",
    "test_dist": "mocha test/dist/ && karma start karma.conf.js --file-variant sha && karma start karma.conf.js --file-variant sha1 && karma start karma.conf.js --file-variant sha256 && karma start karma.conf.js --file-variant sha512 && karma start karma.conf.js --file-variant sha3",
    "prepare": "husky"
  },
  "mocha": {
    "require": [
      "ts-node/register"
    ],
    "timeout": 10000,
    "node-option": [
      "no-experimental-strip-types"
    ]
  },
  "nyc": {
    "require": [
      "ts-node/register"
    ],
    "exclude": [
      "dist",
      "*.js",
      "coverage",
      "test",
      "scripts",
      "rollup.config.mjs"
    ],
    "reporter": [
      "text",
      "html"
    ],
    "all": true
  },
  "prettier": {
    "printWidth": 120,
    "proseWrap": "always",
    "overrides": [
      {
        "files": "*.md",
        "options": {
          "printWidth": 80
        }
      }
    ]
  },
  "lint-staged": {
    "*.{js,ts}": "eslint --cache --fix",
    "*.{js,ts,css,md,html,json}": "prettier --write"
  }
}

[evidence record sha256:6f3fc4c772dc554042af6c167ef331b8a967c6316d3bc947469e5d36640d56a0 kind tool-call:read]
step 3: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"npm run --silent test 2>&1 | head -200","timeoutMs":120000}
tool shell ok: exit code: 0
stdout:


  Test packedLEConcat
    ✔ For 2 0-byte Values
    1) For 2 3-byte Values
    2) For 2 4-byte Values
    3) For 1 1-byte and 1 3-byte Value

  Test parseInputOption
    ✔ For Fully Specified Value
    ✔ For Empty but Optional Value
    ✔ For Empty but Required Value
    ✔ For Value Missing value Key
    ✔ For Value Missing binLen Key

  Test getOutputOpts
    ✔ Empty Input
    ✔ b64Pad Specified
    ✔ outputLen Specified
    ✔ shakeLen Specified
    ✔ Invalid shakeLen
    ✔ Invalid outputLen
    ✔ Invalid b64Pad
    ✔ Invalid outputUpper

  Test jsSHABase
    ✔ Test Constructor with Empty Options
    ✔ Test Constructor with Bad numRounds
    ✔ Test update
    ✔ Test getHash Without Needed outputLen 
    ✔ Test getHash
    ✔ Test getHash for SHAKE
    ✔ Test getHash for numRounds=3
    ✔ Test getHash for SHAKE numRounds=3
    ✔ Test setHMACKey with Short Key
    ✔ Test setHMACKey with Long Key
    ✔ Test setHMACKey Error on Double Call
    ✔ Test setHMACKey Error on numRounds > 1
    ✔ Test setHMACKey Error on After update
    ✔ Test setHMACKey Error on Unsupported Variant
    ✔ Test HMAC Return
    ✔ Test getHMAC Error on Not Setting MAC Key

  Test hex2packed
    ✔ 4-byte Input - No Existing Input
    ✔ 4-byte Input - Existing Input
    ✔ 4-byte Input - No Existing Input with Endian Modifier
    ✔ 4-byte Input - Existing Input with Endian Modifier
    ✔ Invalid Length Exception
    ✔ Invalid Character Exception

  Test b642packed
    ✔ 4-byte Input - No Existing Input
    ✔ 4-byte Input - Existing Input
    ✔ 4-byte Input - No Existing Input with Endian Modifier
    ✔ 4-byte Input - Existing Input with Endian Modifier
    ✔ Invalid '=' Exception
    ✔ Invalid Character Exception

  Test uint8array2packed
    ✔ 4-byte Input - No Existing Input
    ✔ 4-byte Input - Existing Input
    ✔ 4-byte Input - No Existing Input with Endian Modifier
    ✔ 4-byte Input - Existing Input with Endian Modifier

  Test arrayBuffer2packed
    ✔ 4-byte Input - No Existing Input
    ✔ 4-byte Input - Existing Input
    ✔ 4-byte Input - No Existing Input with Endian Modifier
    ✔ 4-byte Input - Existing Input with Endian Modifier

  Test bytes2packed
    ✔ 3-Byte Input - No Existing Input
    ✔ 5-Byte Input - No Existing Input
    ✔ 3-Byte Input - Existing Input
    ✔ 5-Byte Input - Existing Input
    ✔ 3-Byte Input - No Existing Input with Endian Modifier
    ✔ 5-Byte Input - No Existing Input with Endian Modifier
    ✔ 3-Byte Input - Existing Input with Endian Modifier
    ✔ 5-Byte Input - Existing Input with Endian Modifier

  Test str2packed
    ✔ 'ABCDE' Input UTF8 - No Existing Input
    ✔ U+00F1 Input (2 UTF-8 Bytes) UTF8 - No Existing Input
    ✔ U+1E4D Input (3 UTF-8 Bytes) UTF8 - No Existing Input
    ✔ U+10348 Input (4 UTF-8 Bytes, 4 UTF-16 Bytes) UTF8 - No Existing Input
    ✔ 'ABCDE' Input UTF8 - No Existing Input With Endian Modifier
    ✔ U+00F1 Input (2 UTF-8 Bytes) UTF8 - No Existing Input With Endian Modifier
    ✔ U+1E4D Input (3 UTF-8 Bytes) UTF8 - No Existing Input With Endian Modifier
    ✔ U+10348 Input (4 UTF-8 Bytes, 4 UTF-16 Bytes) UTF8 - No Existing Input With Endian Modifier
    ✔ 'ABCDE' Input UTF8 - Existing Input
    ✔ U+00F1 Input (2 UTF-8 Bytes) UTF8 - Existing Input
    ✔ U+1E4D Input (3 UTF-8 Bytes) UTF8 - Existing Input
    ✔ U+10348 Input (4 UTF-8 Bytes, 4 UTF-16 Bytes) UTF8 - Existing Input
    ✔ 'ABCDE' Input UTF16LE - No Existing Input
    ✔ U+00F1 Input (2 UTF-8 Bytes) UTF16LE - No Existing Input
    ✔ U+1E4D Input (3 UTF-8 Bytes) UTF16LE - No Existing Input
    ✔ U+10348 Input (4 UTF-8 Bytes, 4 UTF-16 Bytes) UTF16LE - No Existing Input
    ✔ 'ABCDE' Input UTF16LE - No Existing Input with Endian Modifier
    ✔ U+00F1 Input (2 UTF-8 Bytes) UTF16LE - No Existing Input with Endian Modifier
    ✔ U+1E4D Input (3 UTF-8 Bytes) UTF16LE - No Existing Input with Endian Modifier
    ✔ U+10348 Input (4 UTF-8 Bytes, 4 UTF-16 Bytes) UTF16LE - No Existing Input with Endian Modifier
    ✔ 'ABCDE' Input UTF16LE - Existing Input
    ✔ U+00F1 Input (2 UTF-8 Bytes) UTF16LE - Existing Input
    ✔ U+1E4D Input (3 UTF-8 Bytes) UTF16LE - Existing Input
    ✔ U+10348 Input (4 UTF-8 Bytes, 4 UTF-16 Bytes) UTF16LE - Existing Input
    ✔ 'ABCDE' Input UTF16BE - No Existing Input
    ✔ U+00F1 Input (2 UTF-8 Bytes) UTF16BE - No Existing Input
    ✔ U+1E4D Input (3 UTF-8 Bytes) UTF16BE - No Existing Input
    ✔ U+10348 Input (4 UTF-8 Bytes, 4 UTF-16 Bytes) UTF16BE - No Existing Input
    ✔ 'ABCDE' Input UTF16BE - No Existing Input with Endian Modifier
    ✔ U+00F1 Input (2 UTF-8 Bytes) UTF16BE - No Existing Input with Endian Modifier
    ✔ U+1E4D Input (3 UTF-8 Bytes) UTF16BE - No Existing Input with Endian Modifier
    ✔ U+10348 Input (4 UTF-8 Bytes, 4 UTF-16 Bytes) UTF16BE - No Existing Input with Endian Modifier
    ✔ 'ABCDE' Input UTF16BE - Existing Input
    ✔ U+00F1 Input (2 UTF-8 Bytes) UTF16BE - Existing Input
    ✔ U+1E4D Input (3 UTF-8 Bytes) UTF16BE - Existing Input
    ✔ U+10348 Input (4 UTF-8 Bytes, 4 UTF-16 Bytes) UTF16BE - Existing Input

  Test packed2hex
    ✔ 16-bit Input
    ✔ 64-bit Input
    ✔ 64-bit Input with Output Uppercase
    ✔ 16-bit Input with Endian Modifier
    ✔ 64-bit with Endian Modifier

  Test packed2b64
    ✔ 8-bit Input
    ✔ 16-bit Input
    ✔ 64-bit Input
    ✔ 64-bit Input with # Pad
    ✔ 16-bit Input with Endian Modifier
    ✔ 64-bit with Endian Modifier

  Test packed2bytes
    ✔ 16-bit Input
    ✔ 64-bit Input
    ✔ 16-bit Input with Endian Modifier
    ✔ 64-bit with Endian Modifier

  Test packed2arraybuffer
    ✔ 16-bit Input
    ✔ 64-bit Input
    ✔ 16-bit Input with Endian Modifier
    ✔ 64-bit with Endian Modifier

  Test packed2uint8array
    ✔ 16-bit Input
    ✔ 64-bit Input
    ✔ 16-bit Input with Endian Modifier
    ✔ 64-bit with Endian Modifier

  Test packed2uint8array
    ✔ 16-bit Input
    ✔ 64-bit Input
    ✔ 16-bit Input with Endian Modifier
    ✔ 64-bit with Endian Modifier

  Test getStrConverter
    ✔ hex2packed Mapping
    ✔ b642packed Mapping
    ✔ bytes2packed Mapping
    ✔ arraybuffer2packed Mapping
    ✔ uint8array2packed Mapping
    ✔ str2packed Mapping
    ✔ Invalid UTF Exception
    ✔ Invalid Input Type
    ✔ arraybuffer2packed Unsupported
    ✔ uint8array2packed Unsupported

  Test getOutputConverter
    ✔ packed2hex Mapping
    ✔ packed2b64 Mapping
    ✔ packed2bytes Mapping
    ✔ packed2arraybuffer Mapping
    ✔ packed2uint8array Mapping
    ✔ Invalid Input Type
    ✔ arraybuffer2packed Unsupported
    ✔ uint8array2packed Unsupported

  Test rotl_32
    ✔ With Wrap Around
    ✔ Without Wrap Around

  Test rotr_32
    ✔ With Wrap Around
    ✔ Without Wrap Around

  Test shr_32
    ✔ With Wrap Around
    ✔ Without Wrap Around

  Test parity_32
    ✔ With Valid Inputs

  Test ch_32
    ✔ With Valid Inputs

  Test maj_32
    ✔ With Valid Inputs
[evidence record sha256:2b7be72e19d00c8a09989e5a6c7fad8c136ae3caaaf983bb2e011179661aa798 kind tool-call:shell]
step 4: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"npm run --silent test 2>&1 | tail -300","timeoutMs":120000}
tool shell ok: exit code: 0
stdout:
  9) Test byte_pad
       For 2-byte Value Padded to 8-bytes:

      AssertionError: expected [ 2049, 4386 ] to deeply equal [ 287442945, +0 ]
      + expected - actual

       [
      -  2049
      -  4386
      +  287442945
      +  0
       ]
      
      at Context.<anonymous> (test/src/test_sha3.ts:146:12)
      at processImmediate (node:internal/timers:574:21)

  10) Test byte_pad
       For 4-byte Value Padded to 8-bytes:

      AssertionError: expected [ 2049, 287454020 ] to deeply equal [ 860096513, 4386 ]
      + expected - actual

       [
      -  2049
      -  287454020
      +  860096513
      +  4386
       ]
      
      at Context.<anonymous> (test/src/test_sha3.ts:149:12)
      at processImmediate (node:internal/timers:574:21)

  11) Test byte_pad
       For 6-byte Value Padded to 8-bytes:

      AssertionError: expected [ 2049, 1144201745, 26197, +0 ] to deeply equal [ 571541505, 1716864051 ]
      + expected - actual

       [
      -  2049
      -  1144201745
      -  26197
      -  0
      +  571541505
      +  1716864051
       ]
      
      at Context.<anonymous> (test/src/test_sha3.ts:152:12)
      at processImmediate (node:internal/timers:574:21)

  12) Test jsSHA(SHA3)
       CSHAKE With Customization:

      AssertionError: expected false to be true
      + expected - actual

      -false
      +true
      
      at /work/test/src/test_sha3.ts:491:14
      at /work/src/sha3.ts:218:7
      at Context.<anonymous> (test/src/test_sha3.ts:488:43)
      at processImmediate (node:internal/timers:574:21)

  13) Test jsSHA(SHA3)
       CSHAKE With function-name:

      AssertionError: expected false to be true
      + expected - actual

      -false
      +true
      
      at /work/test/src/test_sha3.ts:501:14
      at /work/src/sha3.ts:218:7
      at Context.<anonymous> (test/src/test_sha3.ts:498:43)
      at processImmediate (node:internal/timers:574:21)

  14) Test jsSHA(SHA3)
       KMAC128 With Customization:

      AssertionError: expected false to be true
      + expected - actual

      -false
      +true
      
      at /work/test/src/test_sha3.ts:514:14
      at /work/src/sha3.ts:218:7
      at Context.<anonymous> (test/src/test_sha3.ts:508:43)
      at processImmediate (node:internal/timers:574:21)

  15) Test jsSHA(CSHAKE128) Using NIST Tests
       Short Data:

      AssertionError: expected 'df3ca130a459b7e37d14a497a876ae4913b42…' to equal 'c1c36925b6409a04f1b504fcbca9d82b40172…'
      + expected - actual

      -df3ca130a459b7e37d14a497a876ae4913b4222cad4569085730d9c48fa6429f
      +c1c36925b6409a04f1b504fcbca9d82b4017277cb5ed2b2065fc1d3814d5aaf5
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  16) Test jsSHA(CSHAKE128) Using NIST Tests
       Long Data:

      AssertionError: expected 'b180dd84e14812e67a09f6b49866694fb5bfe…' to equal 'c5221d50e4f822d96a2e8881a961420f294b7…'
      + expected - actual

      -b180dd84e14812e67a09f6b49866694fb5bfe1e1fa0b619466a328e6cd862cfa
      +c5221d50e4f822d96a2e8881a961420f294b7b24fe3d2094baed2c6524cc166b
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  17) Test jsSHA(CSHAKE128) Using NIST Tests
       Long Data With Long Customization:

      AssertionError: expected 'f6f3b1ed4af7d998fa70d715db32e102769a6…' to equal 'e4e44126332673143120f8f1d160ed103b432…'
      + expected - actual

      -f6f3b1ed4af7d998fa70d715db32e102769a660ab1a71f594dcb508586505f3e
      +e4e44126332673143120f8f1d160ed103b43277787adf64fc5f86ed08f1e01dd
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  18) Test jsSHA(CSHAKE256) Using NIST Tests
       Short Data:

      AssertionError: expected 'a579b4ed515e1cf33a2a578af2d7dac8ef89e…' to equal 'd008828e2b80ac9d2218ffee1d070c48b8e4c…'
      + expected - actual

      -a579b4ed515e1cf33a2a578af2d7dac8ef89ece61b1f901b998ffff8083d389d5d180545cbdc02bedaf7e5176a6ae46b0f36ad5bd158dc001f4c061d717260ee
      +d008828e2b80ac9d2218ffee1d070c48b8e4c87bff32c9699d5b6896eee0edd164020e2be0560858d9c00c037e34a96937c561a74c412bb4c746469527281c8c
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  19) Test jsSHA(CSHAKE256) Using NIST Tests
       Long Data:

      AssertionError: expected '84884235c349ad54eac3ec9c554a7e07c7c55…' to equal '07dc27b11e51fbac75bc7b3c1d983e8b4b85f…'
      + expected - actual

      -84884235c349ad54eac3ec9c554a7e07c7c55e020535bcbbfccfb3b3a4cb54ecd61dbdf2bef557ded23cae584eebb393363c8adfb2b90dac0922a16c69751605
      +07dc27b11e51fbac75bc7b3c1d983e8b4b85fb1defaf218912ac86430273091727f42b17ed1df63e8ec118f04b23633c1dfb1574c8fb55cb45da8e25afb092bb
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  20) Test jsSHA(CSHAKE256) Using NIST Tests
       Long Data With Long Customization:

      AssertionError: expected 'e7263a82699db4131f350171bcb5314c43780…' to equal '6c3bc0d35932de54311706668fea4a03d044b…'
      + expected - actual

      -e7263a82699db4131f350171bcb5314c43780cc6fefb0a45ad464553c07368ca3633f99e8614af65180118b59b6b444b8cea5768cfdc61a9bca56a4cabe30a5c
      +6c3bc0d35932de54311706668fea4a03d044b32ec2ed6cb525d625556c75b130a33d836630d62ac610c4e2c8753783bc5b1046cb95fac0377ec3ee06525651b8
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  21) Test jsSHA(KMAC128) Using NIST Tests
       Short Data Without Customization:

      AssertionError: expected 'e05169ada826129a69db2a53c34506c4c887c…' to equal 'e5780b0d3ea6f7d3a429c5706aa43a00fadbd…'
      + expected - actual

      -e05169ada826129a69db2a53c34506c4c887c1f362a101bff091b4ca0c9543f4
      +e5780b0d3ea6f7d3a429c5706aa43a00fadbd7d49628839e3187243f456ee14e
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  22) Test jsSHA(KMAC128) Using NIST Tests
       Short Data With Customization:

      AssertionError: expected '60c478b210a93c03ce6391cc0e76d35d3ffe6…' to equal '3b1fba963cd8b0b59e8c1a6d71888b7143651…'
      + expected - actual

      -60c478b210a93c03ce6391cc0e76d35d3ffe6bb4ffd67cd6bce51564ff8e5b1f
      +3b1fba963cd8b0b59e8c1a6d71888b7143651af8ba0a7070c0979e2811324aa5
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  23) Test jsSHA(KMAC128) Using NIST Tests
       Long Data With Long Customization:

      AssertionError: expected '75bd9e4acbb60eb78d2d80f1cf81188d92251…' to equal '308edaf1c4ffac004fdd62da5c52011159c0f…'
      + expected - actual

      -75bd9e4acbb60eb78d2d80f1cf81188d92251ecf3e56a5e0fcba9b4abe9f9883
      +308edaf1c4ffac004fdd62da5c52011159c0f45fb6cb564940d95bcec4b8e369
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  24) Test jsSHA(KMAC128) Using NIST Tests
       Long Data With Long Customization With Long Key:

      AssertionError: expected '96b1f3d1413a19e8cd2bb4ed2dcc141e3a46c…' to equal '9a045678281312a1ef3389b22bfcf2bfeb4c3…'
      + expected - actual

      -96b1f3d1413a19e8cd2bb4ed2dcc141e3a46c71327dbeef34d9426a6fa4d761f
      +9a045678281312a1ef3389b22bfcf2bfeb4c38d7c477b315eb3a2f3d929e0736
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  25) Test jsSHA(KMAC128) Using NIST Tests
       Long Data With Customization:

      AssertionError: expected 'f7621ef762c0ecc2935afa3b325016bf31fa9…' to equal '1f5b4e6cca02209e0dcb5ca635b89a15e271e…'
      + expected - actual

      -f7621ef762c0ecc2935afa3b325016bf31fa9d55f65038e046c520d24732c0c1
      +1f5b4e6cca02209e0dcb5ca635b89a15e271ecc760071dfd805faa38f9729230
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  26) Test jsSHA(KMAC256) Using NIST Tests
       Short Data With Customization:

      AssertionError: expected '220fd8e5aafe10d0dba202b552afff80f2b6f…' to equal '20c570c31346f703c9ac36c61c03cb64c3970…'
      + expected - actual

      -220fd8e5aafe10d0dba202b552afff80f2b6f8026d498306118d66b88c86f7d43e9e501d8a86fe01d973435e63461bbb54aeab804b9d6c4a8c77ed7f03ec0d20
      +20c570c31346f703c9ac36c61c03cb64c3970d0cfc787e9b79599d273a68d2f7f69d4cc3de9d104a351689f27cf6f5951f0103f33f4f24871024d9c27773a8dd
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  27) Test jsSHA(KMAC256) Using NIST Tests
       Long Data With Customization:

      AssertionError: expected '075e516b8cb7543913f96a3ae6ec1dfb52f5a…' to equal 'b58618f71f92e1d56c1b8c55ddd7cd188b97b…'
      + expected - actual

      -075e516b8cb7543913f96a3ae6ec1dfb52f5a53384d7e8410946bc01bba0b90e3a1f045b847b0fce32336d6ebf8e5e309eef715842bdbf8778d98d319c9a0bf1
      +b58618f71f92e1d56c1b8c55ddd7cd188b97b4ca4d99831eb2699a837da2e4d970fbacfde50033aea585f1a2708510c32d07880801bd182898fe476876fc8965
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  28) Test jsSHA(KMAC256) Using NIST Tests
       Long Data With Long Customization:

      AssertionError: expected '4b80b63ae9563ac6d58e01c8fb1db1ba1d3b8…' to equal 'af4eaeb69f5fdf34e090baf6eaab1e3985f8b…'
      + expected - actual

      -4b80b63ae9563ac6d58e01c8fb1db1ba1d3b8f320e11fe100685a11e95942a21f863a9909f7ed46356f5451d98850ad648c2573529f369acb0ebf2c19050763a
      +af4eaeb69f5fdf34e090baf6eaab1e3985f8bef02a77b94f60270e7fa6132ca8c899ad2c8e7a7680d9d197039c72ba640a20e5cce978365791502386cd7f13b8
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  29) Test jsSHA(KMAC256) Using NIST Tests
       Long Data With Long Customization With Long Key:

      AssertionError: expected 'd3194c8e93a39ba683959f080b60106e61699…' to equal '4c2cca887c7e632062a421c574f8b9cd26b55…'
      + expected - actual

      -d3194c8e93a39ba683959f080b60106e61699a3208fcf8dc240ece597bbdecadfd866c21f73653f02ed5b05c9c5d8f40e6966247e3d87aa8337cf31116a7956f
      +4c2cca887c7e632062a421c574f8b9cd26b55d1ede7747e7ebc4d0e32f4e5f06814e439a5c0fc26c9312b7e0e4185cd7aedf54292c79480ca854e61a48b72f3c
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)

  30) Test jsSHA(KMAC256) Using NIST Tests
       Long Data Without Customization:

      AssertionError: expected '844d65a9cf9dda23789c7873d5a5c267f77a4…' to equal '75358cf39e41494e949707927cee0af20a3ff…'
      + expected - actual

      -844d65a9cf9dda23789c7873d5a5c267f77a4b62474a815a54ac90cf7e8558a1154aebac7a1122bbc20919b5f16a06fb84c22b5973dacef50925c38d8e1a5542
      +75358cf39e41494e949707927cee0af20a3ff553904c86b08f21cc414bcfd691589d27cf5e15369cbbff8b9a4c2eb17800855d0235ff635da82533ec6b759b69
      
      at Context.<anonymous> (test/src/common.ts:45:18)
      at processImmediate (node:internal/timers:574:21)



------------------|---------|----------|---------|---------|-------------------
File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------------|---------|----------|---------|---------|-------------------
All files         |     100 |      100 |     100 |     100 |                   
 common.ts        |     100 |      100 |     100 |     100 |                   
 converters.ts    |     100 |      100 |     100 |     100 |                   
 primitives_32.ts |     100 |      100 |     100 |     100 |                   
 primitives_64.ts |     100 |      100 |     100 |     100 |                   
 sha.ts           |     100 |      100 |     100 |     100 |                   
 sha1.ts          |     100 |      100 |     100 |     100 |                   
 sha256.ts        |     100 |      100 |     100 |     100 |                   
 sha3.ts          |     100 |      100 |     100 |     100 |                   
 sha512.ts        |     100 |      100 |     100 |     100 |                   
------------------|---------|----------|---------|---------|-------------------
[evidence record sha256:575b1297b81185e24ca0a7e158ef7f15c94bbbd3aae9f855e0356845e1110582 kind tool-call:shell]
step 5: calling local:qwen3.6:35b-a3b
tool list <- {"path":"src"}
tool list ok: common.ts
converters.ts
custom_types.ts
license_header.es3.txt
license_header.es6.txt
primitives_32.ts
primitives_64.ts
sha.ts
sha1.ts
sha256.ts
sha3.ts
sha512.ts
[evidence record sha256:4b77ace854021b6646bf6c03c233f813221632f309e0db02b7146d2d3a6defbf kind tool-call:list]
tool list <- {"path":"test/src"}
tool list ok: common.ts
test_common.ts
test_converters.ts
test_primitives_32.ts
test_primitives_64.ts
test_sha.ts
test_sha1.ts
test_sha256.ts
test_sha3.ts
test_sha3_consts.ts
test_sha512.ts
[evidence record sha256:1fb2862abfc3eeddf6a5a30c071288abea16157698a00610b7f62b4606a6c211 kind tool-call:list]
step 6: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/converters.ts"}
tool read ok: import { packedValue, EncodingType, FormatType } from "./custom_types";
/**
 * Return type for all the *2packed functions
 */
const b64Tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

const arraybuffer_error = "ARRAYBUFFER not supported by this environment";
const uint8array_error = "UINT8ARRAY not supported by this environment";

/**
 * Convert a string to an array of words.
 *
 * There is a known bug with an odd number of existing bytes and using a UTF-16 encoding.  However, this function is
 * used such that the existing bytes are always a result of a previous UTF-16 str2packed call and therefore there 
 * should never be an odd number of existing bytes.

 * @param str Unicode string to be converted to binary representation.
 * @param utfType The Unicode type to use to encode the source string.
 * @param existingPacked A packed int array of bytes to append the results to.
 * @param existingPackedLen The number of bits in `existingPacked`.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @returns Hashmap of the packed values.
 */
function str2packed(
  str: string,
  utfType: EncodingType,
  existingPacked: number[] | undefined,
  existingPackedLen: number | undefined,
  bigEndianMod: -1 | 1,
): packedValue {
  let codePnt,
    codePntArr,
    byteCnt = 0,
    i,
    j,
    intOffset,
    byteOffset,
    shiftModifier,
    transposeBytes;

  existingPackedLen = existingPackedLen || 0;
  const packed = existingPacked || [0],
    existingByteLen = existingPackedLen >>> 3;

  if ("UTF8" === utfType) {
    shiftModifier = bigEndianMod === -1 ? 3 : 0;
    for (i = 0; i < str.length; i += 1) {
      codePnt = str.charCodeAt(i);
      codePntArr = [];

      if (0x80 > codePnt) {
        codePntArr.push(codePnt);
      } else if (0x800 > codePnt) {
        codePntArr.push(0xc0 | (codePnt >>> 6));
        codePntArr.push(0x80 | (codePnt & 0x3f));
      } else if (0xd800 > codePnt || 0xe000 <= codePnt) {
        codePntArr.push(0xe0 | (codePnt >>> 12), 0x80 | ((codePnt >>> 6) & 0x3f), 0x80 | (codePnt & 0x3f));
      } else {
        i += 1;
        codePnt = 0x10000 + (((codePnt & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
        codePntArr.push(
          0xf0 | (codePnt >>> 18),
          0x80 | ((codePnt >>> 12) & 0x3f),
          0x80 | ((codePnt >>> 6) & 0x3f),
          0x80 | (codePnt & 0x3f),
        );
      }

      for (j = 0; j < codePntArr.length; j += 1) {
        byteOffset = byteCnt + existingByteLen;
        intOffset = byteOffset >>> 2;
        while (packed.length <= intOffset) {
          packed.push(0);
        }
        /* Known bug kicks in here */
        packed[intOffset] |= codePntArr[j] << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
        byteCnt += 1;
      }
    }
  } else {
    /* UTF16BE or UTF16LE */
    shiftModifier = bigEndianMod === -1 ? 2 : 0;
    /* Internally strings are UTF-16BE so transpose bytes under two conditions:
     * need LE and not switching endianness due to SHA-3
     * need BE and switching endianness due to SHA-3 */
    transposeBytes = ("UTF16LE" === utfType && bigEndianMod !== 1) || ("UTF16LE" !== utfType && bigEndianMod === 1);
    for (i = 0; i < str.length; i += 1) {
      codePnt = str.charCodeAt(i);
      if (transposeBytes === true) {
        j = codePnt & 0xff;
        codePnt = (j << 8) | (codePnt >>> 8);
      }

      byteOffset = byteCnt + existingByteLen;
      intOffset = byteOffset >>> 2;
      while (packed.length <= intOffset) {
        packed.push(0);
      }
      packed[intOffset] |= codePnt << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
      byteCnt += 2;
    }
  }
  return { value: packed, binLen: byteCnt * 8 + existingPackedLen };
}

/**
 * Convert a hex string to an array of words.
 *
 * @param str Hexadecimal string to be converted to binary representation.
 * @param existingPacked A packed int array of bytes to append the results to.
 * @param existingPackedLen The number of bits in `existingPacked` array.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @returns Hashmap of the packed values.
 */
function hex2packed(
  str: string,
  existingPacked: number[] | undefined,
  existingPackedLen: number | undefined,
  bigEndianMod: -1 | 1,
): packedValue {
  let i, num, intOffset, byteOffset;

  if (0 !== str.length % 2) {
    throw new Error("String of HEX type must be in byte increments");
  }

  existingPackedLen = existingPackedLen || 0;
  const packed = existingPacked || [0],
    existingByteLen = existingPackedLen >>> 3,
    shiftModifier = bigEndianMod === -1 ? 3 : 0;

  for (i = 0; i < str.length; i += 2) {
    num = parseInt(str.substr(i, 2), 16);
    if (!isNaN(num)) {
      byteOffset = (i >>> 1) + existingByteLen;
      intOffset = byteOffset >>> 2;
      while (packed.length <= intOffset) {
        packed.push(0);
      }
      packed[intOffset] |= num << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
    } else {
      throw new Error("String of HEX type contains invalid characters");
    }
  }

  return { value: packed, binLen: str.length * 4 + existingPackedLen };
}

/**
 * Convert a string of raw bytes to an array of words.
 *
 * @param str String of raw bytes to be converted to binary representation.
 * @param existingPacked A packed int array of bytes to append the results to.
 * @param existingPackedLen The number of bits in `existingPacked` array.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @returns Hashmap of the packed values.
 */
function bytes2packed(
  str: string,
  existingPacked: number[] | undefined,
  existingPackedLen: number | undefined,
  bigEndianMod: -1 | 1,
): packedValue {
  let codePnt, i, intOffset, byteOffset;

  existingPackedLen = existingPackedLen || 0;
  const packed = existingPacked || [0],
    existingByteLen = existingPackedLen >>> 3,
    shiftModifier = bigEndianMod === -1 ? 3 : 0;

  for (i = 0; i < str.length; i += 1) {
    codePnt = str.charCodeAt(i);

    byteOffset = i + existingByteLen;
    intOffset = byteOffset >>> 2;
    if (packed.length <= intOffset) {
      packed.push(0);
    }
    packed[intOffset] |= codePnt << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
  }

  return { value: packed, binLen: str.length * 8 + existingPackedLen };
}

/**
 * Convert a base-64 string to an array of words.
 *
 * @param str Base64-encoded string to be converted to binary representation.
 * @param existingPacked A packed int array of bytes to append the results to.
 * @param existingPackedLen The number of bits in `existingPacked` array.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @returns Hashmap of the packed values.
 */
function b642packed(
  str: string,
  existingPacked: number[] | undefined,
  existingPackedLen: number | undefined,
  bigEndianMod: -1 | 1,
): packedValue {
  let byteCnt = 0,
    index,
    i,
    j,
    tmpInt,
    strPart,
    intOffset,
    byteOffset;

  existingPackedLen = existingPackedLen || 0;
  const packed = existingPacked || [0],
    existingByteLen = existingPackedLen >>> 3,
    shiftModifier = bigEndianMod === -1 ? 3 : 0,
    firstEqual = str.indexOf("=");

  if (-1 === str.search(/^[a-zA-Z0-9=+/]+$/)) {
    throw new Error("Invalid character in base-64 string");
  }

  str = str.replace(/=/g, "");
  if (-1 !== firstEqual && firstEqual < str.length) {
    throw new Error("Invalid '=' found in base-64 string");
  }

  for (i = 0; i < str.length; i += 4) {
    strPart = str.substr(i, 4);
    tmpInt = 0;

    for (j = 0; j < strPart.length; j += 1) {
      index = b64Tab.indexOf(strPart.charAt(j));
      tmpInt |= index << (18 - 6 * j);
    }

    for (j = 0; j < strPart.length - 1; j += 1) {
      byteOffset = byteCnt + existingByteLen;
      intOffset = byteOffset >>> 2;
      while (packed.length <= intOffset) {
        packed.push(0);
      }
      packed[intOffset] |=
        ((tmpInt >>> (16 - j * 8)) & 0xff) << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
      byteCnt += 1;
    }
  }

  return { value: packed, binLen: byteCnt * 8 + existingPackedLen };
}

/**
 * Convert an Uint8Array to an array of words.
 *
 * @param arr Uint8Array to be converted to binary representation.
 * @param existingPacked A packed int array of bytes to append the results to.
 * @param existingPackedLen The number of bits in `existingPacked` array.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @returns Hashmap of the packed values.
 */
function uint8array2packed(
  arr: Uint8Array,
  existingPacked: number[] | undefined,
  existingPackedLen: number | undefined,
  bigEndianMod: -1 | 1,
): packedValue {
  let i, intOffset, byteOffset;

  existingPackedLen = existingPackedLen || 0;
  const packed = existingPacked || [0],
    existingByteLen = existingPackedLen >>> 3,
    shiftModifier = bigEndianMod === -1 ? 3 : 0;

  for (i = 0; i < arr.length; i += 1) {
    byteOffset = i + existingByteLen;
    intOffset = byteOffset >>> 2;
    if (packed.length <= intOffset) {
      packed.push(0);
    }
    packed[intOffset] |= arr[i] << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
  }

  return { value: packed, binLen: arr.length * 8 + existingPackedLen };
}

/**
 * Convert an ArrayBuffer to an array of words
 *
 * @param arr ArrayBuffer to be converted to binary representation.
 * @param existingPacked A packed int array of bytes to append the results to.
 * @param existingPackedLen The number of bits in `existingPacked` array.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @returns Hashmap of the packed values.
 */
function arraybuffer2packed(
  arr: ArrayBuffer,
  existingPacked: number[] | undefined,
  existingPackedLen: number | undefined,
  bigEndianMod: -1 | 1,
): packedValue {
  return uint8array2packed(new Uint8Array(arr), existingPacked, existingPackedLen, bigEndianMod);
}

/**
 * Function that takes an input format and UTF encoding and returns the appropriate function used to convert the input.
 *
 * @param format The format of the input to be converted
 * @param utfType The string encoding to use for TEXT inputs.
 * @param bigEndianMod Modifier for whether hash function is big or small endian
 * @returns Function that will convert an input to a packed int array.
 */
export function getStrConverter(
  format: FormatType,
  utfType: EncodingType,
  bigEndianMod: -1 | 1,
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
): (input: any, existingBin?: number[], existingBinLen?: number) => packedValue {
  /* Validate encoding */
  switch (utfType) {
    case "UTF8":
    /* Fallthrough */
    case "UTF16BE":
    /* Fallthrough */
    case "UTF16LE":
      /* Fallthrough */
      break;
    default:
      throw new Error("encoding must be UTF8, UTF16BE, or UTF16LE");
  }

  /* Map inputFormat to the appropriate converter */
  switch (format) {
    case "HEX":
      /**
       * @param str String of hexadecimal bytes to be converted to binary representation.
       * @param existingPacked A packed int array of bytes to append the results to.
       * @param existingPackedLen The number of bits in `existingPacked` array.
       * @returns Hashmap of the packed values.
       */
      return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
        return hex2packed(str, existingBin, existingBinLen, bigEndianMod);
      };
    case "TEXT":
      /**
       * @param str Unicode string to be converted to binary representation.
       * @param existingPacked A packed int array of bytes to append the results to.
       * @param existingPackedLen The number of bits in `existingPacked` array.
       * @returns Hashmap of the packed values.
       */
      return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
        return str2packed(str, utfType, existingBin, existingBinLen, bigEndianMod);
      };
    case "B64":
      /**
       * @param str Base64-encoded string to be converted to binary representation.
       * @param existingPacked A packed int array of bytes to append the results to.
       * @param existingPackedLen The number of bits in `existingPacked` array.
       * @returns Hashmap of the packed values.
       */
      return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
        return b642packed(str, existingBin, existingBinLen, bigEndianMod);
      };
    case "BYTES":
      /**
       * @param str String of raw bytes to be converted to binary representation.
       * @param existingPacked A packed int array of bytes to append the results to.
       * @param existingPackedLen The number of bits in `existingPacked` array.
       * @returns Hashmap of the packed values.
       */
      return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
        return bytes2packed(str, existingBin, existingBinLen, bigEndianMod);
      };
    case "ARRAYBUFFER":
      try {
        new ArrayBuffer(0);
      } catch {
        throw new Error(arraybuffer_error);
      }
      /**
       * @param arr ArrayBuffer to be converted to binary representation.
       * @param existingPacked A packed int array of bytes to append the results to.
       * @param existingPackedLen The number of bits in `existingPacked` array.
       * @returns Hashmap of the packed values.
       */
      return function (arr: ArrayBuffer, existingBin?: number[], existingBinLen?: number): packedValue {
        return arraybuffer2packed(arr, existingBin, existingBinLen, bigEndianMod);
      };
    case "UINT8ARRAY":
      try {
        new Uint8Array(0);
      } catch {
        throw new Error(uint8array_error);
      }
      /**
       * @param arr Uint8Array to be converted to binary representation.
       * @param existingPacked A packed int array of bytes to append the results to.
       * @param existingPackedLen The number of bits in `existingPacked` array.
       * @returns Hashmap of the packed values.
       */
      return function (arr: Uint8Array, existingBin?: number[], existingBinLen?: number): packedValue {
        return uint8array2packed(arr, existingBin, existingBinLen, bigEndianMod);
      };
    default:
      throw new Error("format must be HEX, TEXT, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
  }
}

/**
 * Convert an array of words to a hexadecimal string.
 *
 * toString() won't work here because it removes preceding zeros (e.g. 0x00000001.toString === "1" rather than
 * "00000001" and 0.toString(16) === "0" rather than "00").
 *
 * @param packed Array of integers to be converted.
 * @param outputLength Length of output in bits.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @param formatOpts Hashmap containing validated output formatting options.
 * @returns Hexadecimal representation of `packed`.
 */
export function packed2hex(
  packed: number[],
  outputLength: number,
  bigEndianMod: -1 | 1,
  formatOpts: { outputUpper: boolean; b64Pad: string },
): string {
  const hex_tab = "0123456789abcdef";
  let str = "",
    i,
    srcByte;

  const length = outputLength / 8,
    shiftModifier = bigEndianMod === -1 ? 3 : 0;

  for (i = 0; i < length; i += 1) {
    /* The below is more than a byte but it gets taken care of later */
    srcByte = packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)));
    str += hex_tab.charAt((srcByte >>> 4) & 0xf) + hex_tab.charAt(srcByte & 0xf);
  }

  return formatOpts["outputUpper"] ? str.toUpperCase() : str;
}

/**
 * Convert an array of words to a base-64 string.
 *
 * @param packed Array of integers to be converted.
 * @param outputLength Length of output in bits.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @param formatOpts Hashmap containing validated output formatting options.
 * @returns Base64-encoded representation of `packed`.
 */
export function packed2b64(
  packed: number[],
  outputLength: number,
  bigEndianMod: -1 | 1,
  formatOpts: { outputUpper: boolean; b64Pad: string },
): string {
  let str = "",
    i,
    j,
    triplet,
    int1,
    int2;

  const length = outputLength / 8,
    shiftModifier = bigEndianMod === -1 ? 3 : 0;

  for (i = 0; i < length; i += 3) {
    int1 = i + 1 < length ? packed[(i + 1) >>> 2] : 0;
    int2 = i + 2 < length ? packed[(i + 2) >>> 2] : 0;
    triplet =
      (((packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff) << 16) |
      (((int1 >>> (8 * (shiftModifier + bigEndianMod * ((i + 1) % 4)))) & 0xff) << 8) |
      ((int2 >>> (8 * (shiftModifier + bigEndianMod * ((i + 2) % 4)))) & 0xff);
    for (j = 0; j < 4; j += 1) {
      if (i * 8 + j * 6 <= outputLength) {
        str += b64Tab.charAt((triplet >>> (6 * (3 - j))) & 0x3f);
      } else {
        str += formatOpts["b64Pad"];
      }
    }
  }
  return str;
}

/**
 * Convert an array of words to raw bytes string.
 *
 * @param packed Array of integers to be converted.
 * @param outputLength Length of output in bits.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @returns Raw bytes representation of `packed`.
 */
export function packed2bytes(packed: number[], outputLength: number, bigEndianMod: -1 | 1): string {
  let str = "",
    i,
    srcByte;

  const length = outputLength / 8,
    shiftModifier = bigEndianMod === -1 ? 3 : 0;

  for (i = 0; i < length; i += 1) {
    srcByte = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
    str += String.fromCharCode(srcByte);
  }

  return str;
}

/**
 * Convert an array of words to an ArrayBuffer.
 *
 * @param packed Array of integers to be converted.
 * @param outputLength Length of output in bits.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @returns An ArrayBuffer containing bytes from `packed.
 */
export function packed2arraybuffer(packed: number[], outputLength: number, bigEndianMod: -1 | 1): ArrayBuffer {
  let i;
  const length = outputLength / 8,
    retVal = new ArrayBuffer(length),
    arrView = new Uint8Array(retVal),
    shiftModifier = bigEndianMod === -1 ? 3 : 0;

  for (i = 0; i < length; i += 1) {
    arrView[i] = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
  }

  return retVal;
}

/**
 * Convert an array of words to an Uint8Array.
 *
 * @param packed Array of integers to be converted.
 * @param outputLength Length of output in bits.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @returns An Uint8Array containing bytes from `packed.
 */
export function packed2uint8array(packed: number[], outputLength: number, bigEndianMod: -1 | 1): Uint8Array {
  let i;
  const length = outputLength / 8,
    shiftModifier = bigEndianMod === -1 ? 3 : 0,
    retVal = new Uint8Array(length);

  for (i = 0; i < length; i += 1) {
    retVal[i] = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
  }

  return retVal;
}

/**
 * Function that takes an output format and associated parameters and returns a function that converts packed integers
 * to that format.
 *
 * @param format The desired output formatting.
 * @param outputBinLen Output length in bits.
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @param outputOptions Hashmap of output formatting options
 * @returns Function that will convert a packed integer array to desired format.
 */
export function getOutputConverter(
  format: "HEX" | "B64" | "BYTES",
  outputBinLen: number,
  bigEndianMod: -1 | 1,
  outputOptions: { outputUpper: boolean; b64Pad: string },
): (binarray: number[]) => string;
export function getOutputConverter(
  format: "ARRAYBUFFER",
  outputBinLen: number,
  bigEndianMod: -1 | 1,
  outputOptions: { outputUpper: boolean; b64Pad: string },
): (binarray: number[]) => ArrayBuffer;
export function getOutputConverter(
  format: "UINT8ARRAY",
  outputBinLen: number,
  bigEndianMod: -1 | 1,
  outputOptions: { outputUpper: boolean; b64Pad: string },
): (binarray: number[]) => Uint8Array;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export function getOutputConverter(format: any, outputBinLen: any, bigEndianMod: any, outputOptions: any): any {
  switch (format) {
    case "HEX":
      return function (binarray: number[]): string {
        return packed2hex(binarray, outputBinLen, bigEndianMod, outputOptions);
      };
    case "B64":
      return function (binarray: number[]): string {
        return packed2b64(binarray, outputBinLen, bigEndianMod, outputOptions);
      };
    case "BYTES":
      return function (binarray: number[]): string {
        return packed2bytes(binarray, outputBinLen, bigEndianMod);
      };
    case "ARRAYBUFFER":
      try {
        /* Need to test ArrayBuffer support */
        new ArrayBuffer(0);
      } catch {
        throw new Error(arraybuffer_error);
      }
      return function (binarray: number[]): ArrayBuffer {
        return packed2arraybuffer(binarray, outputBinLen, bigEndianMod);
      };
    case "UINT8ARRAY":
      try {
        /* Need to test Uint8Array support */
        new Uint8Array(0);
      } catch {
        throw new Error(uint8array_error);
      }
      return function (binarray: number[]): Uint8Array {
        return packed2uint8array(binarray, outputBinLen, bigEndianMod);
      };
    default:
      throw new Error("format must be HEX, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
  }
}

[evidence record sha256:2f7121d7c74e7543d6a36aca16dcaa0854454c075fb73b2398860955c7f76d90 kind tool-call:read]
step 7: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/sha3.ts"}
tool read ok: import { jsSHABase, packedLEConcat, sha_variant_error, mac_rounds_error, TWO_PWR_32, parseInputOption } from "./common";
import {
  packedValue,
  CSHAKEOptionsNoEncodingType,
  CSHAKEOptionsEncodingType,
  SHAKEOptionsNoEncodingType,
  SHAKEOptionsEncodingType,
  KMACOptionsNoEncodingType,
  KMACOptionsEncodingType,
  FixedLengthOptionsEncodingType,
  FixedLengthOptionsNoEncodingType,
  FormatNoTextType,
  ResolvedCSHAKEOptionsNoEncodingType,
  ResolvedKMACOptionsNoEncodingType,
} from "./custom_types";
import { getStrConverter } from "./converters";
import { Int_64, rotl_64, xor_64_2, xor_64_5 } from "./primitives_64";

type FixedLengthVariantType = "SHA3-224" | "SHA3-256" | "SHA3-384" | "SHA3-512" | "SHAKE128" | "SHAKE256";

type VariantType = FixedLengthVariantType | "SHAKE128" | "SHAKE256" | "CSHAKE128" | "CSHAKE256" | "KMAC128" | "KMAC256";

const rc_sha3 = [
  new Int_64(0x00000000, 0x00000001),
  new Int_64(0x00000000, 0x00008082),
  new Int_64(0x80000000, 0x0000808a),
  new Int_64(0x80000000, 0x80008000),
  new Int_64(0x00000000, 0x0000808b),
  new Int_64(0x00000000, 0x80000001),
  new Int_64(0x80000000, 0x80008081),
  new Int_64(0x80000000, 0x00008009),
  new Int_64(0x00000000, 0x0000008a),
  new Int_64(0x00000000, 0x00000088),
  new Int_64(0x00000000, 0x80008009),
  new Int_64(0x00000000, 0x8000000a),
  new Int_64(0x00000000, 0x8000808b),
  new Int_64(0x80000000, 0x0000008b),
  new Int_64(0x80000000, 0x00008089),
  new Int_64(0x80000000, 0x00008003),
  new Int_64(0x80000000, 0x00008002),
  new Int_64(0x80000000, 0x00000080),
  new Int_64(0x00000000, 0x0000800a),
  new Int_64(0x80000000, 0x8000000a),
  new Int_64(0x80000000, 0x80008081),
  new Int_64(0x80000000, 0x00008080),
  new Int_64(0x00000000, 0x80000001),
  new Int_64(0x80000000, 0x80008008),
];

const r_sha3 = [
  [0, 36, 3, 41, 18],
  [1, 44, 10, 45, 2],
  [62, 6, 43, 15, 61],
  [28, 55, 25, 21, 56],
  [27, 20, 39, 8, 14],
];

/**
 * Gets the state values for the specified SHA-3 variant.
 *
 * @param _variant Unused for this family.
 * @returns The initial state values.
 */
function getNewState(_variant: VariantType): Int_64[][] {
  let i;
  const retVal = [];

  for (i = 0; i < 5; i += 1) {
    retVal[i] = [new Int_64(0, 0), new Int_64(0, 0), new Int_64(0, 0), new Int_64(0, 0), new Int_64(0, 0)];
  }

  return retVal;
}

/**
 * Returns a clone of the given SHA3 state.
 *
 * @param state The state to be cloned.
 * @returns The cloned state.
 */
function cloneSHA3State(state: Int_64[][]): Int_64[][] {
  let i;
  const clone = [];
  for (i = 0; i < 5; i += 1) {
    clone[i] = state[i].slice();
  }

  return clone;
}

/**
 * Performs a round of SHA-3 hashing over a block. This clobbers `state`.
 *
 * @param block The binary array representation of the block to hash.
 * @param state Hash state from a previous round.
 * @returns The resulting state value.
 */
function roundSHA3(block: number[] | null, state: Int_64[][]): Int_64[][] {
  let round, x, y, B;
  const C = [],
    D = [];

  if (null !== block) {
    for (x = 0; x < block.length; x += 2) {
      state[(x >>> 1) % 5][((x >>> 1) / 5) | 0] = xor_64_2(
        state[(x >>> 1) % 5][((x >>> 1) / 5) | 0],
        new Int_64(block[x + 1], block[x]),
      );
    }
  }

  for (round = 0; round < 24; round += 1) {
    /* Any SHA-3 variant name will do here */
    B = getNewState("SHA3-384");

    /* Perform theta step */
    for (x = 0; x < 5; x += 1) {
      C[x] = xor_64_5(state[x][0], state[x][1], state[x][2], state[x][3], state[x][4]);
    }
    for (x = 0; x < 5; x += 1) {
      D[x] = xor_64_2(C[(x + 4) % 5], rotl_64(C[(x + 1) % 5], 1));
    }
    for (x = 0; x < 5; x += 1) {
      for (y = 0; y < 5; y += 1) {
        state[x][y] = xor_64_2(state[x][y], D[x]);
      }
    }

    /* Perform combined ro and pi steps */
    for (x = 0; x < 5; x += 1) {
      for (y = 0; y < 5; y += 1) {
        B[y][(2 * x + 3 * y) % 5] = rotl_64(state[x][y], r_sha3[x][y]);
      }
    }

    /* Perform chi step */
    for (x = 0; x < 5; x += 1) {
      for (y = 0; y < 5; y += 1) {
        state[x][y] = xor_64_2(
          B[x][y],
          new Int_64(
            ~B[(x + 1) % 5][y].highOrder & B[(x + 2) % 5][y].highOrder,
            ~B[(x + 1) % 5][y].lowOrder & B[(x + 2) % 5][y].lowOrder,
          ),
        );
      }
    }

    /* Perform iota step */
    state[0][0] = xor_64_2(state[0][0], rc_sha3[round]);
  }

  return state;
}

/**
 * Finalizes the SHA-3 hash. This clobbers `remainder` and `state`.
 *
 * @param remainder Any leftover unprocessed packed ints that still need to be processed.
 * @param remainderBinLen The number of bits in `remainder`.
 * @param _processedBinLen Unused for this family.
 * @param state The state from a previous round.
 * @param blockSize The block size/rate of the variant in bits
 * @param delimiter The delimiter value for the variant
 * @param outputLen The output length for the variant in bits
 * @returns The array of integers representing the SHA-3 hash of message.
 */
function finalizeSHA3(
  remainder: number[],
  remainderBinLen: number,
  _processedBinLen: number,
  state: Int_64[][],
  blockSize: number,
  delimiter: number,
  outputLen: number,
): number[] {
  let i,
    state_offset = 0,
    temp;
  const retVal = [],
    binaryStringInc = blockSize >>> 5,
    remainderIntLen = remainderBinLen >>> 5;

  /* Process as many blocks as possible, some may be here for multiple rounds
		with SHAKE
	*/
  for (i = 0; i < remainderIntLen && remainderBinLen >= blockSize; i += binaryStringInc) {
    state = roundSHA3(remainder.slice(i, i + binaryStringInc), state);
    remainderBinLen -= blockSize;
  }

  remainder = remainder.slice(i);
  remainderBinLen = remainderBinLen % blockSize;

  /* Pad out the remainder to a full block */
  while (remainder.length < binaryStringInc) {
    remainder.push(0);
  }

  /* Find the next "empty" byte for the 0x80 and append it via an xor */
  i = remainderBinLen >>> 3;
  remainder[i >> 2] ^= delimiter << (8 * (i % 4));

  remainder[binaryStringInc - 1] ^= 0x80000000;
  state = roundSHA3(remainder, state);

  while (retVal.length * 32 < outputLen) {
    temp = state[state_offset % 5][(state_offset / 5) | 0];
    retVal.push(temp.lowOrder);
    if (retVal.length * 32 >= outputLen) {
      break;
    }
    retVal.push(temp.highOrder);
    state_offset += 1;

    if (0 === (state_offset * 64) % blockSize) {
      roundSHA3(null, state);
      state_offset = 0;
    }
  }

  return retVal;
}

/**
 * Performs NIST left_encode function returned with no extra garbage bits. `x` is limited to <= 9007199254740991.
 *
 * @param x 32-bit number to to encode.
 * @returns The NIST specified output of the function.
 */
function left_encode(x: number): packedValue {
  let byteOffset,
    byte,
    numEncodedBytes = 0;
  /* JavaScript numbers max out at 0x1FFFFFFFFFFFFF (7 bytes) so this will return a maximum of 7 + 1 = 8 bytes */
  const retVal = [0, 0],
    x_64 = [x & 0xffffffff, (x / TWO_PWR_32) & 0x1fffff];

  for (byteOffset = 6; byteOffset >= 0; byteOffset--) {
    /* This will surprisingly work for large shifts because JavaScript masks the shift amount by 0x1F */
    byte = (x_64[byteOffset >> 2] >>> (8 * byteOffset)) & 0xff;

    /* Starting from the most significant byte of a 64-bit number, start recording the first non-0 byte and then
       every byte thereafter */
    if (byte !== 0 || numEncodedBytes !== 0) {
      retVal[(numEncodedBytes + 1) >> 2] |= byte << ((numEncodedBytes + 1) * 8);
      numEncodedBytes += 1;
    }
  }
  numEncodedBytes = numEncodedBytes !== 0 ? numEncodedBytes : 1;
  retVal[0] |= numEncodedBytes;

  return { value: numEncodedBytes + 1 > 4 ? retVal : [retVal[0]], binLen: 8 + numEncodedBytes * 8 };
}

/**
 * Performs NIST right_encode function returned with no extra garbage bits. `x` is limited to <= 9007199254740991.
 *
 * @param x 32-bit number to to encode.
 * @returns The NIST specified output of the function.
 */
function right_encode(x: number): packedValue {
  let byteOffset,
    byte,
    numEncodedBytes = 0;
  /* JavaScript numbers max out at 0x1FFFFFFFFFFFFF (7 bytes) so this will return a maximum of 7 + 1 = 8 bytes */
  const retVal = [0, 0],
    x_64 = [x & 0xffffffff, (x / TWO_PWR_32) & 0x1fffff];

  for (byteOffset = 6; byteOffset >= 0; byteOffset--) {
    /* This will surprisingly work for large shifts because JavaScript masks the shift amount by 0x1F */
    byte = (x_64[byteOffset >> 2] >>> (8 * byteOffset)) & 0xff;

    /* Starting from the most significant byte of a 64-bit number, start recording the first non-0 byte and then
       every byte thereafter */
    if (byte !== 0 || numEncodedBytes !== 0) {
      retVal[numEncodedBytes >> 2] |= byte << (numEncodedBytes * 8);
      numEncodedBytes += 1;
    }
  }
  numEncodedBytes = numEncodedBytes !== 0 ? numEncodedBytes : 1;
  retVal[numEncodedBytes >> 2] |= numEncodedBytes << (numEncodedBytes * 8);

  return { value: numEncodedBytes + 1 > 4 ? retVal : [retVal[0]], binLen: 8 + numEncodedBytes * 8 };
}

/**
 * Performs NIST encode_string function.
 *
 * @param input Packed array of integers.
 * @returns NIST encode_string output.
 */
function encode_string(input: packedValue): packedValue {
  return packedLEConcat(left_encode(input["binLen"]), input);
}

/**
 * Performs NIST byte_pad function.
 *
 * @param packed Packed array of integers.
 * @param outputByteLen Desired length of the output in bytes, assumed to be a multiple of 4.
 * @returns NIST byte_pad output.
 */
function byte_pad(packed: packedValue, outputByteLen: number): number[] {
  let encodedLen = left_encode(outputByteLen),
    i;

  encodedLen = packedLEConcat(encodedLen, packed);
  const outputIntLen = outputByteLen >>> 2,
    intsToAppend = (outputIntLen - (encodedLen["value"].length % outputIntLen)) % outputIntLen;

  for (i = 0; i < intsToAppend; i++) {
    encodedLen["value"].push(0);
  }

  return encodedLen["value"];
}

/**
 * Parses/validate constructor options for a CSHAKE variant
 *
 * @param options Option given to constructor
 */
function resolveCSHAKEOptions(options: CSHAKEOptionsNoEncodingType): ResolvedCSHAKEOptionsNoEncodingType {
  const resolvedOptions = options || {};

  return {
    funcName: parseInputOption("funcName", resolvedOptions["funcName"], 1, { value: [], binLen: 0 }),
    customization: parseInputOption("Customization", resolvedOptions["customization"], 1, { value: [], binLen: 0 }),
  };
}

/**
 * Parses/validate constructor options for a KMAC variant
 *
 * @param options Option given to constructor
 */
function resolveKMACOptions(options: KMACOptionsNoEncodingType): ResolvedKMACOptionsNoEncodingType {
  const resolvedOptions = options || {};

  return {
    kmacKey: parseInputOption("kmacKey", resolvedOptions["kmacKey"], 1),
    /* This is little-endian packed "KMAC" */
    funcName: { value: [0x43414d4b], binLen: 32 },
    customization: parseInputOption("Customization", resolvedOptions["customization"], 1, { value: [], binLen: 0 }),
  };
}

export default class jsSHA extends jsSHABase<Int_64[][], VariantType> {
  intermediateState: Int_64[][];
  variantBlockSize: number;
  bigEndianMod: -1 | 1;
  outputBinLen: number;
  isVariableLen: boolean;
  HMACSupported: boolean;

  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  converterFunc: (input: any, existingBin: number[], existingBinLen: number) => packedValue;
  roundFunc: (block: number[], H: Int_64[][]) => Int_64[][];
  finalizeFunc: (
    remainder: number[],
    remainderBinLen: number,
    processedBinLen: number,
    H: Int_64[][],
    outputLen: number,
  ) => number[];
  stateCloneFunc: (state: Int_64[][]) => Int_64[][];
  newStateFunc: (variant: VariantType) => Int_64[][];
  getMAC: ((options: { outputLen: number }) => number[]) | null;

  constructor(variant: FixedLengthVariantType, inputFormat: "TEXT", options?: FixedLengthOptionsEncodingType);
  constructor(
    variant: FixedLengthVariantType,
    inputFormat: FormatNoTextType,
    options?: FixedLengthOptionsNoEncodingType,
  );
  constructor(variant: "SHAKE128" | "SHAKE256", inputFormat: "TEXT", options?: SHAKEOptionsEncodingType);
  constructor(variant: "SHAKE128" | "SHAKE256", inputFormat: FormatNoTextType, options?: SHAKEOptionsNoEncodingType);
  constructor(variant: "CSHAKE128" | "CSHAKE256", inputFormat: "TEXT", options?: CSHAKEOptionsEncodingType);
  constructor(variant: "CSHAKE128" | "CSHAKE256", inputFormat: FormatNoTextType, options?: CSHAKEOptionsNoEncodingType);
  constructor(variant: "KMAC128" | "KMAC256", inputFormat: "TEXT", options: KMACOptionsEncodingType);
  constructor(variant: "KMAC128" | "KMAC256", inputFormat: FormatNoTextType, options: KMACOptionsNoEncodingType);
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  constructor(variant: any, inputFormat: any, options?: any) {
    let delimiter = 0x06,
      variantBlockSize = 0;
    super(variant, inputFormat, options);
    const resolvedOptions = options || {};

    /* In other variants, this was done after variable initialization but need to do it earlier here becaue we want to
       avoid KMAC initialization */
    if (this.numRounds !== 1) {
      if (resolvedOptions["kmacKey"] || resolvedOptions["hmacKey"]) {
        throw new Error(mac_rounds_error);
      } else if (this.shaVariant === "CSHAKE128" || this.shaVariant === "CSHAKE256") {
        throw new Error("Cannot set numRounds for CSHAKE variants");
      }
    }

    this.bigEndianMod = 1;
    this.converterFunc = getStrConverter(this.inputFormat, this.utfType, this.bigEndianMod);
    this.roundFunc = roundSHA3;
    this.stateCloneFunc = cloneSHA3State;
    this.newStateFunc = getNewState;
    this.intermediateState = getNewState(variant);

    this.isVariableLen = false;
    switch (variant) {
      case "SHA3-224":
        this.variantBlockSize = variantBlockSize = 1152;
        this.outputBinLen = 224;
        this.HMACSupported = true;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getHMAC;
        break;
      case "SHA3-256":
        this.variantBlockSize = variantBlockSize = 1088;
        this.outputBinLen = 256;
        this.HMACSupported = true;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getHMAC;
        break;
      case "SHA3-384":
        this.variantBlockSize = variantBlockSize = 832;
        this.outputBinLen = 384;
        this.HMACSupported = true;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getHMAC;
        break;
      case "SHA3-512":
        this.variantBlockSize = variantBlockSize = 576;
        this.outputBinLen = 512;
        this.HMACSupported = true;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getHMAC;
        break;
      case "SHAKE128":
        delimiter = 0x1f;
        this.variantBlockSize = variantBlockSize = 1344;
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        this.getMAC = null;
        break;
      case "SHAKE256":
        delimiter = 0x1f;
        this.variantBlockSize = variantBlockSize = 1088;
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        this.getMAC = null;
        break;
      case "KMAC128":
        delimiter = 0x4;
        this.variantBlockSize = variantBlockSize = 1344;
        this._initializeKMAC(options);
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getKMAC;
        break;
      case "KMAC256":
        delimiter = 0x4;
        this.variantBlockSize = variantBlockSize = 1088;
        this._initializeKMAC(options);
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getKMAC;
        break;
      case "CSHAKE128":
        this.variantBlockSize = variantBlockSize = 1344;
        delimiter = this._initializeCSHAKE(options);
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        this.getMAC = null;
        break;
      case "CSHAKE256":
        this.variantBlockSize = variantBlockSize = 1088;
        delimiter = this._initializeCSHAKE(options);
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        this.getMAC = null;
        break;
      default:
        throw new Error(sha_variant_error);
    }

    /* This needs to be down here as CSHAKE can change its delimiter */
    this.finalizeFunc = function (remainder, remainderBinLen, processedBinLen, state, outputBinLen): number[] {
      return finalizeSHA3(
        remainder,
        remainderBinLen,
        processedBinLen,
        state,
        variantBlockSize,
        delimiter,
        outputBinLen,
      );
    };

    if (resolvedOptions["hmacKey"]) {
      this._setHMACKey(parseInputOption("hmacKey", resolvedOptions["hmacKey"], this.bigEndianMod));
    }
  }

  /**
   * Initialize CSHAKE variants.
   *
   * @param options Options containing CSHAKE params.
   * @param funcNameOverride Overrides any "funcName" present in `options` (used with KMAC)
   * @returns The delimiter to be used
   */
  protected _initializeCSHAKE(options?: CSHAKEOptionsNoEncodingType, funcNameOverride?: packedValue): number {
    const resolvedOptions = resolveCSHAKEOptions(options || {});
    if (funcNameOverride) {
      resolvedOptions["funcName"] = funcNameOverride;
    }
    const packedParams = packedLEConcat(
      encode_string(resolvedOptions["funcName"]),
      encode_string(resolvedOptions["customization"]),
    );

    /* CSHAKE is defined to be a call to SHAKE iff both the customization and function-name string are both empty.  This
       can be accomplished by processing nothing in this step. */
    if (resolvedOptions["customization"]["binLen"] !== 0 || resolvedOptions["funcName"]["binLen"] !== 0) {
      const byte_pad_out = byte_pad(packedParams, this.variantBlockSize >>> 3);
      for (let i = 0; i < byte_pad_out.length; i += this.variantBlockSize >>> 5) {
        this.intermediateState = this.roundFunc(
          byte_pad_out.slice(i, i + (this.variantBlockSize >>> 5)),
          this.intermediateState,
        );
        this.processedLen += this.variantBlockSize;
      }
      return 0x04;
    } else {
      return 0x1f;
    }
  }

  /**
   * Initialize KMAC variants.
   *
   * @param options Options containing KMAC params.
   */
  protected _initializeKMAC(options: KMACOptionsNoEncodingType): void {
    const resolvedOptions = resolveKMACOptions(options || {});

    this._initializeCSHAKE(options, resolvedOptions["funcName"]);
    const byte_pad_out = byte_pad(encode_string(resolvedOptions["kmacKey"]), this.variantBlockSize >>> 3);
    for (let i = 0; i < byte_pad_out.length; i += this.variantBlockSize >>> 5) {
      this.intermediateState = this.roundFunc(
        byte_pad_out.slice(i, i + (this.variantBlockSize >>> 5)),
        this.intermediateState,
      );
      this.processedLen += this.variantBlockSize;
    }
    this.macKeySet = true;
  }

  /**
   * Returns the the KMAC in the specified format.
   *
   * @param options Hashmap of extra outputs options. `outputLen` must be specified.
   * @returns The KMAC in the format specified.
   */
  protected _getKMAC(options: { outputLen: number }): number[] {
    const concatedRemainder = packedLEConcat(
      { value: this.remainder.slice(), binLen: this.remainderLen },
      right_encode(options["outputLen"]),
    );

    return this.finalizeFunc(
      concatedRemainder["value"],
      concatedRemainder["binLen"],
      this.processedLen,
      this.stateCloneFunc(this.intermediateState),
      options["outputLen"],
    );
  }
}

[evidence record sha256:dc067243e6d5276325141aa6c3a1ebe8c9ca55a0f96353dad942adeed4e36f36 kind tool-call:read]
step 8: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/common.ts"}
tool read ok: import { getStrConverter, getOutputConverter } from "./converters";

import {
  FormatType,
  EncodingType,
  FixedLengthOptionsEncodingType,
  FixedLengthOptionsNoEncodingType,
  FormatNoTextType,
  packedValue,
  GenericInputType,
} from "./custom_types";

export const TWO_PWR_32 = 4294967296;

/* Constant used in SHA-2 families */
export const K_sha2 = [
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98,
  0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
  0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8,
  0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
  0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
  0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
  0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
  0xc67178f2,
];

/* Constant used in SHA-2 families */
export const H_trunc = [0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4];

/* Constant used in SHA-2 families */
export const H_full = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];

export const sha_variant_error = "Chosen SHA variant is not supported";
export const mac_rounds_error = "Cannot set numRounds with MAC";

/**
 * Concatenates 2 packed arrays. Clobbers array `a`.
 *
 * @param a First array to concatenate.
 * @param b Second array to concatenate.
 * @returns The concatentation of `a` + `b`.
 */
export function packedLEConcat(a: packedValue, b: packedValue): packedValue {
  let i, arrOffset;
  const aByteLen = a["binLen"] >>> 3,
    bByteLen = b["binLen"] >>> 3,
    leftShiftAmount = aByteLen << 3,
    rightShiftAmount = (4 - aByteLen) << 3;

  /* If a only contains "full" integers, we can just use concat which is so much easier */
  if (aByteLen % 4 === 0) {
    for (i = 0; i < bByteLen; i += 4) {
      arrOffset = (aByteLen + i) >>> 2;
      /* Left shift chops off bits over 32-bits */
      a["value"][arrOffset] |= b["value"][i >>> 2] << leftShiftAmount;
      a["value"].push(0);
      a["value"][arrOffset + 1] |= b["value"][i >>> 2] >>> rightShiftAmount;
    }

    /* Since an unconditional push was performed above, we may have pushed an extra value if it could have been
       encoded without it.  Check if popping an int off (reducing total length by 4 bytes) is still bigger than the
       needed size. */
    if ((a["value"].length << 2) - 4 >= bByteLen + aByteLen) {
      a["value"].pop();
    }

    return { value: a["value"], binLen: a["binLen"] + b["binLen"] };
  } else {
    return { value: a["value"].concat(b["value"]), binLen: a["binLen"] + b["binLen"] };
  }
}

/**
 * Validate hash list containing output formatting options, ensuring presence of every option or adding the default
 * value.
 *
 * @param options Hashmap of output formatting options from user.
 * @returns Validated hashmap containing output formatting options.
 */
export function getOutputOpts(options?: {
  outputUpper?: boolean;
  b64Pad?: string;
  shakeLen?: number;
  outputLen?: number;
}): { outputUpper: boolean; b64Pad: string; outputLen: number } {
  const retVal = { outputUpper: false, b64Pad: "=", outputLen: -1 },
    outputOptions: { outputUpper?: boolean; b64Pad?: string; shakeLen?: number; outputLen?: number } = options || {},
    lenErrstr = "Output length must be a multiple of 8";

  retVal["outputUpper"] = outputOptions["outputUpper"] || false;

  if (outputOptions["b64Pad"]) {
    retVal["b64Pad"] = outputOptions["b64Pad"];
  }

  if (outputOptions["outputLen"]) {
    if (outputOptions["outputLen"] % 8 !== 0) {
      throw new Error(lenErrstr);
    }
    retVal["outputLen"] = outputOptions["outputLen"];
  } else if (outputOptions["shakeLen"]) {
    if (outputOptions["shakeLen"] % 8 !== 0) {
      throw new Error(lenErrstr);
    }
    retVal["outputLen"] = outputOptions["shakeLen"];
  }

  if ("boolean" !== typeof retVal["outputUpper"]) {
    throw new Error("Invalid outputUpper formatting option");
  }

  if ("string" !== typeof retVal["b64Pad"]) {
    throw new Error("Invalid b64Pad formatting option");
  }

  return retVal;
}

/**
 * Parses an external constructor object and returns a packed number, if possible.
 *
 * @param key The human-friendly key name to prefix any errors with
 * @param value The input value object to parse
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @param fallback Fallback value if `value` is undefined.  If not present and `value` is undefined, an Error is thrown
 */
export function parseInputOption(
  key: string,
  value: GenericInputType | undefined,
  bigEndianMod: -1 | 1,
  fallback?: packedValue,
): packedValue {
  const errStr = key + " must include a value and format";
  if (!value) {
    if (!fallback) {
      throw new Error(errStr);
    }
    return fallback;
  }

  if (typeof value["value"] === "undefined" || !value["format"]) {
    throw new Error(errStr);
  }

  return getStrConverter(
    value["format"],
    // @ts-ignore - the value of encoding gets value checked by getStrConverter
    value["encoding"] || "UTF8",
    bigEndianMod,
  )(value["value"]);
}

export abstract class jsSHABase<StateT, VariantT> {
  /**
   * @param variant The desired SHA variant.
   * @param inputFormat The input format to be used in future `update` calls.
   * @param options Hashmap of extra input options.
   */
  /* Needed inputs */
  protected readonly shaVariant: VariantT;
  protected readonly inputFormat: FormatType;
  protected readonly utfType: EncodingType;
  protected readonly numRounds: number;

  /* State */
  protected abstract intermediateState: StateT;
  protected keyWithIPad: number[];
  protected keyWithOPad: number[];
  protected remainder: number[];
  protected remainderLen: number;
  protected updateCalled: boolean;
  protected processedLen: number;
  protected macKeySet: boolean;

  /* Variant specifics */
  protected abstract readonly variantBlockSize: number;
  protected abstract readonly bigEndianMod: -1 | 1;
  protected abstract readonly outputBinLen: number;
  protected abstract readonly isVariableLen: boolean;
  protected abstract readonly HMACSupported: boolean;

  /* Functions */
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  protected abstract readonly converterFunc: (input: any, existingBin: number[], existingBinLen: number) => packedValue;
  protected abstract readonly roundFunc: (block: number[], H: StateT) => StateT;
  protected abstract readonly finalizeFunc: (
    remainder: number[],
    remainderBinLen: number,
    processedBinLen: number,
    H: StateT,
    outputLen: number,
  ) => number[];
  protected abstract readonly stateCloneFunc: (state: StateT) => StateT;
  protected abstract readonly newStateFunc: (variant: VariantT) => StateT;
  protected abstract readonly getMAC: ((options: { outputLen: number }) => number[]) | null;

  protected constructor(variant: VariantT, inputFormat: "TEXT", options?: FixedLengthOptionsEncodingType);
  protected constructor(variant: VariantT, inputFormat: FormatNoTextType, options?: FixedLengthOptionsNoEncodingType);
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  protected constructor(variant: any, inputFormat: any, options?: any) {
    const inputOptions = options || {};
    this.inputFormat = inputFormat;

    this.utfType = inputOptions["encoding"] || "UTF8";
    this.numRounds = inputOptions["numRounds"] || 1;

    // @ts-ignore - The spec actually says ToString is called on the first parseInt argument so it's OK to use it here
    // to check if an arugment is an integer. This cheat would break if it's used to get the value of the argument.
    if (isNaN(this.numRounds) || this.numRounds !== parseInt(this.numRounds, 10) || 1 > this.numRounds) {
      throw new Error("numRounds must a integer >= 1");
    }

    this.shaVariant = variant;
    this.remainder = [];
    this.remainderLen = 0;
    this.updateCalled = false;
    this.processedLen = 0;
    this.macKeySet = false;
    this.keyWithIPad = [];
    this.keyWithOPad = [];
  }

  /**
   * Hashes as many blocks as possible.  Stores the rest for either a future update or getHash call.
   *
   * @param srcString The input to be hashed.
   * @returns A reference to the object.
   */
  update(srcString: string | ArrayBuffer | Uint8Array): this {
    let i,
      updateProcessedLen = 0;
    const variantBlockIntInc = this.variantBlockSize >>> 5,
      convertRet = this.converterFunc(srcString, this.remainder, this.remainderLen),
      chunkBinLen = convertRet["binLen"],
      chunk = convertRet["value"],
      chunkIntLen = chunkBinLen >>> 5;

    for (i = 0; i < chunkIntLen; i += variantBlockIntInc) {
      if (updateProcessedLen + this.variantBlockSize <= chunkBinLen) {
        this.intermediateState = this.roundFunc(chunk.slice(i, i + variantBlockIntInc), this.intermediateState);
        updateProcessedLen += this.variantBlockSize;
      }
    }
    this.processedLen += updateProcessedLen;
    this.remainder = chunk.slice(updateProcessedLen >>> 5);
    this.remainderLen = chunkBinLen % this.variantBlockSize;
    this.updateCalled = true;

    return this;
  }

  /**
   * Returns the desired SHA hash of the input fed in via `update` calls.
   *
   * @param format The desired output formatting
   * @param options Hashmap of output formatting options. `outputLen` must be specified for variable length hashes.
   *   `outputLen` replaces the now deprecated `shakeLen` key.
   * @returns The hash in the format specified.
   */
  getHash(format: "HEX", options?: { outputUpper?: boolean; outputLen?: number; shakeLen?: number }): string;
  getHash(format: "B64", options?: { b64Pad?: string; outputLen?: number; shakeLen?: number }): string;
  getHash(format: "BYTES", options?: { outputLen?: number; shakeLen?: number }): string;
  getHash(format: "UINT8ARRAY", options?: { outputLen?: number; shakeLen?: number }): Uint8Array;
  getHash(format: "ARRAYBUFFER", options?: { outputLen?: number; shakeLen?: number }): ArrayBuffer;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  getHash(format: any, options?: any): any {
    let i,
      finalizedState,
      outputBinLen = this.outputBinLen;

    const outputOptions = getOutputOpts(options);

    if (this.isVariableLen) {
      if (outputOptions["outputLen"] === -1) {
        throw new Error("Output length must be specified in options");
      }
      outputBinLen = outputOptions["outputLen"];
    }

    const formatFunc = getOutputConverter(format, outputBinLen, this.bigEndianMod, outputOptions);
    if (this.macKeySet && this.getMAC) {
      return formatFunc(this.getMAC(outputOptions));
    }

    finalizedState = this.finalizeFunc(
      this.remainder.slice(),
      this.remainderLen,
      this.processedLen,
      this.stateCloneFunc(this.intermediateState),
      outputBinLen,
    );
    for (i = 1; i < this.numRounds; i += 1) {
      /* Need to mask out bits that should be zero due to output not being a multiple of 32 */
      if (this.isVariableLen && outputBinLen % 32 !== 0) {
        finalizedState[finalizedState.length - 1] &= 0x00ffffff >>> (24 - (outputBinLen % 32));
      }
      finalizedState = this.finalizeFunc(
        finalizedState,
        outputBinLen,
        0,
        this.newStateFunc(this.shaVariant),
        outputBinLen,
      );
    }

    return formatFunc(finalizedState);
  }

  /**
   * Sets the HMAC key for an eventual `getHMAC` call.  Must be called immediately after jsSHA object instantiation.
   *
   * @param key The key used to calculate the HMAC
   * @param inputFormat The format of key.
   * @param options Hashmap of extra input options.
   */
  setHMACKey(key: string, inputFormat: "TEXT", options?: { encoding?: EncodingType }): void;
  setHMACKey(key: string, inputFormat: "B64" | "HEX" | "BYTES"): void;
  setHMACKey(key: ArrayBuffer, inputFormat: "ARRAYBUFFER"): void;
  setHMACKey(key: Uint8Array, inputFormat: "UINT8ARRAY"): void;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  setHMACKey(key: any, inputFormat: any, options?: any): void {
    if (!this.HMACSupported) {
      throw new Error("Variant does not support HMAC");
    }

    if (this.updateCalled) {
      throw new Error("Cannot set MAC key after calling update");
    }

    const keyOptions = options || {},
      keyConverterFunc = getStrConverter(inputFormat, keyOptions["encoding"] || "UTF8", this.bigEndianMod);

    this._setHMACKey(keyConverterFunc(key));
  }

  /**
   * Internal function that sets the MAC key.
   *
   * @param key The packed MAC key to use
   */
  protected _setHMACKey(key: packedValue): void {
    const blockByteSize = this.variantBlockSize >>> 3,
      lastArrayIndex = blockByteSize / 4 - 1;
    let i;
    if (this.numRounds !== 1) {
      throw new Error(mac_rounds_error);
    }

    if (this.macKeySet) {
      throw new Error("MAC key already set");
    }

    /* Figure out what to do with the key based on its size relative to
     * the hash's block size */
    if (blockByteSize < key["binLen"] / 8) {
      key["value"] = this.finalizeFunc(
        key["value"],
        key["binLen"],
        0,
        this.newStateFunc(this.shaVariant),
        this.outputBinLen,
      );
    }
    while (key["value"].length <= lastArrayIndex) {
      key["value"].push(0);
    }
    /* Create ipad and opad */
    for (i = 0; i <= lastArrayIndex; i += 1) {
      this.keyWithIPad[i] = key["value"][i] ^ 0x36363636;
      this.keyWithOPad[i] = key["value"][i] ^ 0x5c5c5c5c;
    }

    this.intermediateState = this.roundFunc(this.keyWithIPad, this.intermediateState);
    this.processedLen = this.variantBlockSize;

    this.macKeySet = true;
  }

  /**
   * Returns the the HMAC in the specified format using the key given by a previous `setHMACKey` call.
   *
   * @param format The desired output formatting.
   * @param options Hashmap of extra outputs options.
   * @returns The HMAC in the format specified.
   */
  getHMAC(format: "HEX", options?: { outputUpper?: boolean }): string;
  getHMAC(format: "B64", options?: { b64Pad?: string }): string;
  getHMAC(format: "BYTES"): string;
  getHMAC(format: "UINT8ARRAY"): Uint8Array;
  getHMAC(format: "ARRAYBUFFER"): ArrayBuffer;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  getHMAC(format: any, options?: any): any {
    const outputOptions = getOutputOpts(options),
      formatFunc = getOutputConverter(format, this.outputBinLen, this.bigEndianMod, outputOptions);

    return formatFunc(this._getHMAC());
  }

  /**
   * Internal function that returns the "raw" HMAC
   */
  protected _getHMAC(): number[] {
    let finalizedState;

    if (!this.macKeySet) {
      throw new Error("Cannot call getHMAC without first setting MAC key");
    }

    const firstHash = this.finalizeFunc(
      this.remainder.slice(),
      this.remainderLen,
      this.processedLen,
      this.stateCloneFunc(this.intermediateState),
      this.outputBinLen,
    );
    finalizedState = this.roundFunc(this.keyWithOPad, this.newStateFunc(this.shaVariant));
    finalizedState = this.finalizeFunc(
      firstHash,
      this.outputBinLen,
      this.variantBlockSize,
      finalizedState,
      this.outputBinLen,
    );

    return finalizedState;
  }
}

[evidence record sha256:c40e9d6e85b1e2aac4e8f00a13eac2d9bf7b163a5ff1df73687e22397aceefca kind tool-call:read]
step 9: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"cd /work && cat test/src/test_converters.ts | head -80"}
tool shell failed: denied: "cd /work && cat test/src/test_converters.ts | head -80" is not on the shell allowlist. Confirmation was declined.
[evidence record sha256:72e06a4fcc1ec30e9a656cdebb0059fd164331b2b6fea48fa28a3fc47b6cccc9 kind tool-call:shell]
step 10: calling local:qwen3.6:35b-a3b
tool read <- {"path":"test/src/test_converters.ts"}
tool read ok: import { describe, it } from "mocha";
import rewire from "rewire";
import sinon from "sinon";
import { assert } from "chai";
import { packedValue } from "../../src/custom_types";

const converters = rewire("../../src/converters");

function newArrayBuffer(bytes: number[]): ArrayBuffer {
  const ab = new ArrayBuffer(bytes.length),
    ua = new Uint8Array(ab);
  for (let i = 0; i < ua.length; i++) {
    ua[i] = bytes[i];
  }
  return ab;
}

const toPackedTests = [
  {
    name: "4-byte Input",
    inputs: {
      hex: "41424344",
      b64: "QUJDRA==",
      arrayBuffer: newArrayBuffer([0x41, 0x42, 0x43, 0x44]),
      uint8Array: Uint8Array.from([0x41, 0x42, 0x43, 0x44]),
      existing: [0x45000000],
      existingMod: [0x00000045],
      lengthExisting: 8,
    },
    outputs: {
      original: [0x41424344],
      existing: [0x45414243, 0x44000000],
      originalMod: [0x44434241],
      existingMod: [0x43424145, 0x00000044],
      length: 32,
    },
  },
];

function toPackedTestsBuilder(
  funcToTest: (
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    input: any,
    existingBin: number[] | undefined,
    existingBinLen: number | undefined,
    bigEndianMod: -1 | 1,
  ) => packedValue,
  inputType: "hex" | "b64" | "arrayBuffer" | "uint8Array",
): void {
  toPackedTests.forEach((test) => {
    it(`${test.name} - No Existing Input`, () => {
      assert.deepEqual(funcToTest(test.inputs[inputType], undefined, undefined, -1), {
        value: test.outputs.original,
        binLen: test.outputs.length,
      });
    });
    it(`${test.name} - Existing Input`, () => {
      assert.deepEqual(
        funcToTest(test.inputs[inputType], test.inputs.existing.slice(), test.inputs.lengthExisting, -1),
        {
          value: test.outputs.existing,
          binLen: test.outputs.length + test.inputs.lengthExisting,
        },
      );
    });
    it(`${test.name} - No Existing Input with Endian Modifier`, () => {
      assert.deepEqual(funcToTest(test.inputs[inputType], undefined, undefined, 1), {
        value: test.outputs.originalMod,
        binLen: test.outputs.length,
      });
    });
    it(`${test.name} - Existing Input with Endian Modifier`, () => {
      assert.deepEqual(
        funcToTest(test.inputs[inputType], test.inputs.existingMod.slice(), test.inputs.lengthExisting, 1),
        {
          value: test.outputs.existingMod,
          binLen: test.outputs.length + test.inputs.lengthExisting,
        },
      );
    });
  });
}

describe("Test hex2packed", () => {
  const hex2packed = converters.__get__("hex2packed");

  // Basic I/O tests
  toPackedTestsBuilder(hex2packed, "hex");

  // Input Validation Tests
  it("Invalid Length Exception", () => {
    assert.throws(() => {
      hex2packed("F", undefined, undefined, -1);
    }, "String of HEX type must be in byte increments");
  });
  it("Invalid Character Exception", () => {
    assert.throws(() => {
      hex2packed("GG", [], 0 - 1);
    }, "String of HEX type contains invalid characters");
  });
});

describe("Test b642packed", () => {
  const b642packed = converters.__get__("b642packed");

  // Basic I/O tests
  toPackedTestsBuilder(b642packed, "b64");

  // Input Validation Tests
  it("Invalid '=' Exception", () => {
    assert.throws(() => {
      b642packed("=F", undefined, undefined, -1);
    }, "Invalid '=' found in base-64 string");
  });
  it("Invalid Character Exception", () => {
    assert.throws(() => {
      b642packed("$", undefined, undefined, -1);
    }, "Invalid character in base-64 string");
  });
});

describe("Test uint8array2packed", () => {
  const uint8array2packed = converters.__get__("uint8array2packed");

  toPackedTestsBuilder(uint8array2packed, "uint8Array");
});

describe("Test arrayBuffer2packed", () => {
  const arraybuffer2packed = converters.__get__("arraybuffer2packed");

  toPackedTestsBuilder(arraybuffer2packed, "arrayBuffer");
});

describe("Test bytes2packed", () => {
  const bytes2packed = converters.__get__("bytes2packed"),
    shortBytes = String.fromCharCode(1, 2, 3),
    longBytes = String.fromCharCode(1, 2, 3, 4, 5);

  it("3-Byte Input - No Existing Input", () => {
    assert.deepEqual(bytes2packed(shortBytes, undefined, undefined, -1), {
      value: [0x01020300],
      binLen: 24,
    });
  });

  it("5-Byte Input - No Existing Input", () => {
    assert.deepEqual(bytes2packed(longBytes, undefined, undefined, -1), {
      value: [0x01020304, 0x05000000],
      binLen: 40,
    });
  });

  it("3-Byte Input - Existing Input", () => {
    assert.deepEqual(bytes2packed(shortBytes, [0x05000000], 8, -1), {
      value: [0x05010203],
      binLen: 32,
    });
  });

  it("5-Byte Input - Existing Input", () => {
    assert.deepEqual(bytes2packed(longBytes, [0x06000000], 8, -1), {
      value: [0x06010203, 0x04050000],
      binLen: 48,
    });
  });

  it("3-Byte Input - No Existing Input with Endian Modifier", () => {
    assert.deepEqual(bytes2packed(shortBytes, undefined, undefined, 1), {
      value: [0x00030201],
      binLen: 24,
    });
  });

  it("5-Byte Input - No Existing Input with Endian Modifier", () => {
    assert.deepEqual(bytes2packed(longBytes, undefined, undefined, 1), {
      value: [0x04030201, 0x00000005],
      binLen: 40,
    });
  });

  it("3-Byte Input - Existing Input with Endian Modifier", () => {
    assert.deepEqual(bytes2packed(shortBytes, [0x00000004], 8, 1), {
      value: [0x03020104],
      binLen: 32,
    });
  });

  it("5-Byte Input - Existing Input with Endian Modifier", () => {
    assert.deepEqual(bytes2packed(longBytes, [0x00000006], 8, 1), {
      value: [0x03020106, 0x00000504],
      binLen: 48,
    });
  });
});

describe("Test str2packed", () => {
  const str2packed = converters.__get__("str2packed"),
    toPackedTextTests = [
      {
        name: "'ABCDE' Input",
        inputs: {
          string: "ABCDE",
          existing: [0x46470000],
          existingMod: [0x00004746],
          lengthExisting: 16,
        },
        outputs: {
          utf8: [0x41424344, 0x45000000],
          utf8Mod: [0x44434241, 0x00000045],
          utf8Existing: [0x46474142, 0x43444500],
          lengthUtf8: 40,
          utf16le: [0x41004200, 0x43004400, 0x45000000],
          utf16leMod: [0x00420041, 0x00440043, 0x00000045],
          utf16leExisting: [0x46474100, 0x42004300, 0x44004500],
          utf16be: [0x00410042, 0x00430044, 0x00450000],
          utf16beMod: [0x42004100, 0x44004300, 0x00004500],
          utf16beExisting: [0x46470041, 0x00420043, 0x00440045],
          lengthUtf16: 80,
        },
      },
      {
        name: "U+00F1 Input (2 UTF-8 Bytes)",
        inputs: {
          string: "\u00F1",
          existing: [0x46470000],
          existingMod: [0x00004746],
          lengthExisting: 16,
        },
        outputs: {
          utf8: [0xc3b10000 | 0],
          utf8Mod: [0x0000b1c3],
          utf8Existing: [0x4647c3b1],
          lengthUtf8: 16,
          utf16le: [0xf1000000 | 0],
          utf16leMod: [0x000000f1 | 0],
          utf16leExisting: [0x4647f100],
          utf16be: [0x00f10000],
          utf16beMod: [0x0000f100],
          utf16beExisting: [0x464700f1],
          lengthUtf16: 16,
        },
      },
      {
        name: "U+1E4D Input (3 UTF-8 Bytes)",
        inputs: {
          string: "\u1E4D",
          existing: [0x46470000],
          existingMod: [0x00004746],
          lengthExisting: 16,
        },
        outputs: {
          utf8: [0xe1b98d00 | 0],
          utf8Mod: [0x008db9e1],
          utf8Existing: [0x4647e1b9, 0x8d000000 | 0],
          lengthUtf8: 24,
          utf16le: [0x4d1e0000],
          utf16leMod: [0x00001e4d],
          utf16leExisting: [0x46474d1e],
          utf16be: [0x1e4d0000],
          utf16beMod: [0x00004d1e],
          utf16beExisting: [0x46471e4d],
          lengthUtf16: 16,
        },
      },
      {
        name: "U+10348 Input (4 UTF-8 Bytes, 4 UTF-16 Bytes)",
        inputs: {
          string: "𐍈",
          existing: [0x46470000],
          existingMod: [0x00004746],
          lengthExisting: 16,
        },
        outputs: {
          utf8: [0xf0908d88 | 0],
          utf8Mod: [0x888d90f0 | 0],
          utf8Existing: [0x4647f090, 0x8d880000 | 0],
          lengthUtf8: 32,
          utf16le: [0x00d848df],
          utf16leMod: [0xdf48d800 | 0],
          utf16leExisting: [0x464700d8, 0x48df0000],
          utf16be: [0xd800df48 | 0],
          utf16beMod: [0x48df00d8 | 0],
          utf16beExisting: [0x4647d800, 0xdf480000 | 0],
          lengthUtf16: 32,
        },
      },
    ];

  toPackedTextTests.forEach((test) => {
    it(`${test.name} UTF8 - No Existing Input`, () => {
      assert.deepEqual(str2packed(test.inputs.string, "UTF8", undefined, undefined, -1), {
        value: test.outputs.utf8,
        binLen: test.outputs.lengthUtf8,
      });
    });
  });

  toPackedTextTests.forEach((test) => {
    it(`${test.name} UTF8 - No Existing Input With Endian Modifier`, () => {
      assert.deepEqual(str2packed(test.inputs.string, "UTF8", undefined, undefined, 1), {
        value: test.outputs.utf8Mod,
        binLen: test.outputs.lengthUtf8,
      });
    });
  });

  toPackedTextTests.forEach((test) => {
    it(`${test.name} UTF8 - Existing Input`, () => {
      assert.deepEqual(
        str2packed(test.inputs.string, "UTF8", test.inputs.existing.slice(), test.inputs.lengthExisting, -1),
        {
          value: test.outputs.utf8Existing,
          binLen: test.outputs.lengthUtf8 + test.inputs.lengthExisting,
        },
      );
    });
  });

  toPackedTextTests.forEach((test) => {
    it(`${test.name} UTF16LE - No Existing Input`, () => {
      assert.deepEqual(str2packed(test.inputs.string, "UTF16LE", undefined, undefined, -1), {
        value: test.outputs.utf16le,
        binLen: test.outputs.lengthUtf16,
      });
    });
  });

  toPackedTextTests.forEach((test) => {
    it(`${test.name} UTF16LE - No Existing Input with Endian Modifier`, () => {
      assert.deepEqual(str2packed(test.inputs.string, "UTF16LE", undefined, undefined, 1), {
        value: test.outputs.utf16leMod,
        binLen: test.outputs.lengthUtf16,
      });
    });
  });

  toPackedTextTests.forEach((test) => {
    it(`${test.name} UTF16LE - Existing Input`, () => {
      assert.deepEqual(
        str2packed(test.inputs.string, "UTF16LE", test.inputs.existing.slice(), test.inputs.lengthExisting, -1),
        {
          value: test.outputs.utf16leExisting,
          binLen: test.outputs.lengthUtf16 + test.inputs.lengthExisting,
        },
      );
    });
  });

  toPackedTextTests.forEach((test) => {
    it(`${test.name} UTF16BE - No Existing Input`, () => {
      assert.deepEqual(str2packed(test.inputs.string, "UTF16BE", undefined, undefined, -1), {
        value: test.outputs.utf16be,
        binLen: test.outputs.lengthUtf16,
      });
    });
  });

  toPackedTextTests.forEach((test) => {
    it(`${test.name} UTF16BE - No Existing Input with Endian Modifier`, () => {
      assert.deepEqual(str2packed(test.inputs.string, "UTF16BE", undefined, undefined, 1), {
        value: test.outputs.utf16beMod,
        binLen: test.outputs.lengthUtf16,
      });
    });
  });

  toPackedTextTests.forEach((test) => {
    it(`${test.name} UTF16BE - Existing Input`, () => {
      assert.deepEqual(
        str2packed(test.inputs.string, "UTF16BE", test.inputs.existing.slice(), test.inputs.lengthExisting, -1),
        {
          value: test.outputs.utf16beExisting,
          binLen: test.outputs.lengthUtf16 + test.inputs.lengthExisting,
        },
      );
    });
  });
});

// Input value to be reused across all the packed2* tests
const packedToInput = [0x00112233, 0xaabbccdd];

describe("Test packed2hex", () => {
  const packed2hex = converters.__get__("packed2hex");

  it("16-bit Input", () => {
    assert.equal(packed2hex(packedToInput, 16, -1, { outputUpper: false }), "0011");
  });
  it("64-bit Input", () => {
    assert.equal(packed2hex(packedToInput, 64, -1, { outputUpper: false }), "00112233aabbccdd");
  });
  it("64-bit Input with Output Uppercase", () => {
    assert.equal(packed2hex(packedToInput, 64, -1, { outputUpper: true }), "00112233AABBCCDD");
  });
  it("16-bit Input with Endian Modifier", () => {
    assert.equal(packed2hex(packedToInput, 16, 1, { outputUpper: false }), "3322");
  });
  it("64-bit with Endian Modifier", () => {
    assert.equal(packed2hex(packedToInput, 64, 1, { outputUpper: false }), "33221100ddccbbaa");
  });
});

describe("Test packed2b64", () => {
  const packed2b64 = converters.__get__("packed2b64");

  it("8-bit Input", () => {
    assert.equal(packed2b64(packedToInput, 8, -1, { b64Pad: "=" }), "AA==");
  });
  it("16-bit Input", () => {
    assert.equal(packed2b64(packedToInput, 16, -1, { b64Pad: "=" }), "ABE=");
  });
  it("64-bit Input", () => {
    assert.equal(packed2b64(packedToInput, 64, -1, { b64Pad: "=" }), "ABEiM6q7zN0=");
  });
  it("64-bit Input with # Pad", () => {
    assert.equal(packed2b64(packedToInput, 64, -1, { b64Pad: "#" }), "ABEiM6q7zN0#");
  });
  it("16-bit Input with Endian Modifier", () => {
    assert.equal(packed2b64(packedToInput, 16, 1, { b64Pad: "=" }), "MyI=");
  });
  it("64-bit with Endian Modifier", () => {
    assert.equal(packed2b64(packedToInput, 64, 1, { b64Pad: "=" }), "MyIRAN3Mu6o=");
  });
});

describe("Test packed2bytes", () => {
  const packed2bytes = converters.__get__("packed2bytes");

  it("16-bit Input", () => {
    assert.equal(packed2bytes(packedToInput, 16, -1), String.fromCharCode(0, 0x11));
  });
  it("64-bit Input", () => {
    assert.equal(packed2bytes(packedToInput, 64, -1), String.fromCharCode(0, 0x11, 0x22, 0x33, 0xaa, 0xbb, 0xcc, 0xdd));
  });
  it("16-bit Input with Endian Modifier", () => {
    assert.equal(packed2bytes(packedToInput, 16, 1), String.fromCharCode(0x33, 0x22));
  });
  it("64-bit with Endian Modifier", () => {
    assert.equal(packed2bytes(packedToInput, 64, 1), String.fromCharCode(0x33, 0x22, 0x11, 0, 0xdd, 0xcc, 0xbb, 0xaa));
  });
});

describe("Test packed2arraybuffer", () => {
  const packed2arraybuffer = converters.__get__("packed2arraybuffer");

  it("16-bit Input", () => {
    assert.deepEqual(packed2arraybuffer(packedToInput, 16, -1), newArrayBuffer([0, 0x11]));
  });
  it("64-bit Input", () => {
    assert.deepEqual(
      packed2arraybuffer(packedToInput, 64, -1),
      newArrayBuffer([0, 0x11, 0x22, 0x33, 0xaa, 0xbb, 0xcc, 0xdd]),
    );
  });
  it("16-bit Input with Endian Modifier", () => {
    assert.deepEqual(packed2arraybuffer(packedToInput, 16, 1), newArrayBuffer([0x33, 0x22]));
  });
  it("64-bit with Endian Modifier", () => {
    assert.deepEqual(
      packed2arraybuffer(packedToInput, 64, 1),
      newArrayBuffer([0x33, 0x22, 0x11, 0, 0xdd, 0xcc, 0xbb, 0xaa]),
    );
  });
});

describe("Test packed2uint8array", () => {
  const packed2uint8array = converters.__get__("packed2uint8array");

  it("16-bit Input", () => {
    assert.deepEqual(packed2uint8array(packedToInput, 16, -1), Uint8Array.from([0, 0x11]));
  });
  it("64-bit Input", () => {
    assert.deepEqual(
      packed2uint8array(packedToInput, 64, -1),
      Uint8Array.from([0, 0x11, 0x22, 0x33, 0xaa, 0xbb, 0xcc, 0xdd]),
    );
  });
  it("16-bit Input with Endian Modifier", () => {
    assert.deepEqual(packed2uint8array(packedToInput, 16, 1), Uint8Array.from([0x33, 0x22]));
  });
  it("64-bit with Endian Modifier", () => {
    assert.deepEqual(
      packed2uint8array(packedToInput, 64, 1),
      Uint8Array.from([0x33, 0x22, 0x11, 0, 0xdd, 0xcc, 0xbb, 0xaa]),
    );
  });
});

describe("Test packed2uint8array", () => {
  const packed2uint8array = converters.__get__("packed2uint8array");

  it("16-bit Input", () => {
    assert.deepEqual(packed2uint8array(packedToInput, 16, -1), Uint8Array.from([0, 0x11]));
  });
  it("64-bit Input", () => {
    assert.deepEqual(
      packed2uint8array(packedToInput, 64, -1),
      Uint8Array.from([0, 0x11, 0x22, 0x33, 0xaa, 0xbb, 0xcc, 0xdd]),
    );
  });
  it("16-bit Input with Endian Modifier", () => {
    assert.deepEqual(packed2uint8array(packedToInput, 16, 1), Uint8Array.from([0x33, 0x22]));
  });
  it("64-bit with Endian Modifier", () => {
    assert.deepEqual(
      packed2uint8array(packedToInput, 64, 1),
      Uint8Array.from([0x33, 0x22, 0x11, 0, 0xdd, 0xcc, 0xbb, 0xaa]),
    );
  });
});

describe("Test getStrConverter", () => {
  let revert, strConverter;

  // getStrConverter is actually exported but mixing rewire and sinon imports causes a node.js core dump
  const getStrConverter = converters.__get__("getStrConverter"),
    funcNameToInputValueMappings = [
      { inputValue: "HEX", funcName: "hex2packed" },
      { inputValue: "B64", funcName: "b642packed" },
      { inputValue: "BYTES", funcName: "bytes2packed" },
      { inputValue: "ARRAYBUFFER", funcName: "arraybuffer2packed" },
      { inputValue: "UINT8ARRAY", funcName: "uint8array2packed" },
    ];

  funcNameToInputValueMappings.forEach((mapping) => {
    it(`${mapping.funcName} Mapping`, () => {
      const spy = sinon.spy();
      revert = converters.__set__(mapping.funcName, spy);
      strConverter = getStrConverter(mapping.inputValue, "UTF8", -1);
      strConverter("00", [], 0);
      assert.isTrue(spy.calledWithExactly("00", [], 0, -1));
      revert();
    });
  });

  // Needed to be handled separately due to utf type being passed into eventual function
  it("str2packed Mapping", () => {
    const spy = sinon.spy();
    converters.__with__({ str2packed: spy })(() => {
      strConverter = getStrConverter("TEXT", "UTF8", -1);
      strConverter("00", [], 0);
      assert.isTrue(spy.calledWithExactly("00", "UTF8", [], 0, -1));
    });
  });

  it("Invalid UTF Exception", () => {
    assert.throws(() => {
      // @ts-ignore - Deliberate bad UTF value to test exceptions
      getStrConverter("HEX", "UTF32", -1);
    }, "encoding must be UTF8, UTF16BE, or UTF16LE");
  });

  it("Invalid Input Type", () => {
    assert.throws(() => {
      getStrConverter("GARBAGE", "UTF8", -1);
    }, "format must be HEX, TEXT, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
  });

  it("arraybuffer2packed Unsupported", () => {
    converters.__with__({ ArrayBuffer: sinon.stub().throws() })(() => {
      assert.throws(() => {
        getStrConverter("ARRAYBUFFER", "UTF8", -1);
      }, "ARRAYBUFFER not supported by this environment");
    });
  });

  it("uint8array2packed Unsupported", () => {
    converters.__with__({ Uint8Array: sinon.stub().throws() })(() => {
      assert.throws(() => {
        getStrConverter("UINT8ARRAY", "UTF8", -1);
      }, "UINT8ARRAY not supported by this environment");
    });
  });
});

describe("Test getOutputConverter", () => {
  let spy, revert, outputConverter;

  // getOutputConverter is actually exported but mixing rewire and sinon imports causes a node.js core dump
  const getOutputConverter = converters.__get__("getOutputConverter"),
    funcNameToInputValueMappings = [
      { inputValue: "HEX", funcName: "packed2hex", needsOptions: true },
      { inputValue: "B64", funcName: "packed2b64", needsOptions: true },
      { inputValue: "BYTES", funcName: "packed2bytes", needsOptions: false },
      { inputValue: "ARRAYBUFFER", funcName: "packed2arraybuffer", needsOptions: false },
      { inputValue: "UINT8ARRAY", funcName: "packed2uint8array", needsOptions: false },
    ],
    options = { outputUpper: false, b64Pad: "", outputLen: -1 };

  funcNameToInputValueMappings.forEach((mapping) => {
    it(`${mapping.funcName} Mapping`, () => {
      spy = sinon.spy();
      revert = converters.__set__(mapping.funcName, spy);
      outputConverter = getOutputConverter(mapping.inputValue, 0, -1, options);
      outputConverter([0]);
      if (mapping.needsOptions === true) {
        assert.isTrue(spy.calledWithExactly([0], 0, -1, options));
      } else {
        assert.isTrue(spy.calledWithExactly([0], 0, -1));
      }
      revert();
    });
  });

  it("Invalid Input Type", () => {
    assert.throws(() => {
      getOutputConverter("GARBAGE", -1, -1, options);
    }, "HEX, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
  });

  it("arraybuffer2packed Unsupported", () => {
    converters.__with__({ ArrayBuffer: sinon.stub().throws() })(() => {
      assert.throws(() => {
        getOutputConverter("ARRAYBUFFER", 0, -1, options);
      }, "ARRAYBUFFER not supported by this environment");
    });
  });

  it("uint8array2packed Unsupported", () => {
    converters.__with__({ Uint8Array: sinon.stub().throws() })(() => {
      assert.throws(() => {
        getOutputConverter("UINT8ARRAY", 0, -1, options);
      }, "UINT8ARRAY not supported by this environment");
    });
  });
});

[evidence record sha256:a75900899f5d7814dd5dfa25d0e29a0fa0c15ee1321f627239ee4dbe8572ab24 kind tool-call:read]
step 11: calling local:qwen3.6:35b-a3b
tool read <- {"path":"test/src/test_sha3.ts"}
tool read ok: import { describe, it } from "mocha";
import { assert } from "chai";
import rewire from "rewire";
import sinon from "sinon";
import { runHashTests } from "./common";
import { Int_64 } from "../../src/primitives_64";
import {
  CSHAKEOptionsNoEncodingType,
  CSHAKEOptionsEncodingType,
  KMACOptionsNoEncodingType,
  KMACOptionsEncodingType,
  FixedLengthOptionsEncodingType,
  FixedLengthOptionsNoEncodingType,
  FormatNoTextType,
} from "../../src/custom_types";
import {
  NISTCSHAKERoundIn,
  CSHAKEWithFuncRoundIn,
  newState,
  NISTSHA3Round1In,
  NISTSHA3Round1Out,
  NISTSHA3Round2In,
  NISTSHA3Round2Out,
  SHAKE128Len2048Out,
  NISTKMACCustomizationRound1In,
  NISTKMACCustomizationRound2In,
} from "./test_sha3_consts";

const sha3 = rewire("../../src/sha3");

type VariantNoCSHAKEType = "SHA3-224" | "SHA3-256" | "SHA3-384" | "SHA3-512" | "SHAKE128" | "SHAKE256";

const getNewState = sha3.__get__("getNewState");

describe("Test left_encode", () => {
  const left_encode = sha3.__get__("left_encode");
  it("For 0-byte Value", () => {
    assert.deepEqual(left_encode(0), { value: [0x00000001], binLen: 16 });
  });
  it("For 1-byte Value", () => {
    assert.deepEqual(left_encode(0x11), { value: [0x000001101], binLen: 16 });
  });
  it("For 2-byte Value", () => {
    assert.deepEqual(left_encode(0x1122), { value: [0x000221102], binLen: 24 });
  });
  it("For 3-byte Value", () => {
    assert.deepEqual(left_encode(0x112233), { value: [0x33221103], binLen: 32 });
  });
  it("For 4-byte Value", () => {
    assert.deepEqual(left_encode(0x11223344), { value: [0x33221104, 0x00000044], binLen: 40 });
  });
  it("For 7-byte Value", () => {
    /* 4822678189205111 === 0x0011223344556677 */
    assert.deepEqual(left_encode(4822678189205111), { value: [0x33221107 | 0, 0x77665544 | 0], binLen: 64 });
  });
});

describe("Test right_encode", () => {
  const right_encode = sha3.__get__("right_encode");
  it("For 0-byte Value", () => {
    assert.deepEqual(right_encode(0), { value: [0x00000100], binLen: 16 });
  });
  it("For 1-byte Value", () => {
    assert.deepEqual(right_encode(0x11), { value: [0x000000111], binLen: 16 });
  });
  it("For 2-byte Value", () => {
    assert.deepEqual(right_encode(0x1122), { value: [0x00022211], binLen: 24 });
  });
  it("For 3-byte Value", () => {
    assert.deepEqual(right_encode(0x112233), { value: [0x03332211], binLen: 32 });
  });
  it("For 4-byte Value", () => {
    assert.deepEqual(right_encode(0x11223344), { value: [0x44332211, 0x00000004], binLen: 40 });
  });
  it("For 7-byte Value", () => {
    /* 4822678189205111 === 0x0011223344556677 */
    assert.deepEqual(right_encode(4822678189205111), { value: [0x44332211 | 0, 0x07776655], binLen: 64 });
  });
});

describe("Test encode_string", () => {
  let i, arr: number[];
  const encode_string = sha3.__get__("encode_string");

  it("For 0-bit Input", () => {
    assert.deepEqual(encode_string({ value: [], binLen: 0 }), { value: [0x00000001], binLen: 16 });
  });

  it("For 16-bit Input", () => {
    /* This checks values that can be encoded in a single int */
    assert.deepEqual(encode_string({ value: [0x1122], binLen: 16 }), { value: [0x11221001], binLen: 32 });
  });

  it("For 24-bit Input", () => {
    /* This checks values that can be encoded in 2 ints (and left_encode returns a 16-bit value) */
    assert.deepEqual(encode_string({ value: [0x112233], binLen: 24 }), { value: [0x22331801, 0x00000011], binLen: 40 });
  });

  it("For 256-bit Input", () => {
    /* This hits on the case that left_encode returns a 24-bit value */
    arr = [];
    const retVal = [0x41000102];
    for (i = 0; i < 8; i++) {
      arr.push(0x41414141);
    }
    for (i = 0; i < 7; i++) {
      retVal.push(0x41414141);
    }
    retVal.push(0x00414141);
    assert.deepEqual(encode_string({ value: arr, binLen: 256 }), { value: retVal, binLen: 280 });
  });

  it("For 65536-bit Input", () => {
    /* This hits on the case that left_encode returns a 32-bit value */
    arr = [];
    for (i = 0; i < 2048; i++) {
      arr.push(0x41414141);
    }
    assert.deepEqual(encode_string({ value: arr, binLen: 65536 }), { value: [0x00000103].concat(arr), binLen: 65568 });
  });

  it("For 16777216-bit Input", () => {
    /* This hits on the case that left_encode returns a 40-bit value */
    arr = [];
    for (i = 0; i < 524288; i++) {
      arr.push(0x41414141);
    }
    const retVal = encode_string({ value: arr, binLen: 16777216 });

    /* It's extremely time prohibitive to check all the middle bits so just check the interesting ends */
    assert.equal(retVal["value"][0], [0x00000104]);
    assert.equal(retVal["value"][1], [0x41414100]);
    assert.equal(retVal["value"].length, 524288 + 2);
    assert.equal(retVal["value"][retVal["value"].length - 1], [0x00000041]);
    assert.equal(retVal["binLen"], 16777256);
  });
});

describe("Test byte_pad", () => {
  const byte_pad = sha3.__get__("byte_pad");
  it("For 2-byte Value Padded to 4-bytes", () => {
    assert.deepEqual(byte_pad({ value: [0x00001122], binLen: 16 }, 4), [0x11220401]);
  });

  it("For 2-byte Value Padded to 8-bytes", () => {
    assert.deepEqual(byte_pad({ value: [0x00001122], binLen: 16 }, 8), [0x11220801, 0]);
  });
  it("For 4-byte Value Padded to 8-bytes", () => {
    assert.deepEqual(byte_pad({ value: [0x11223344], binLen: 32 }, 8), [0x33440801, 0x00001122]);
  });
  it("For 6-byte Value Padded to 8-bytes", () => {
    assert.deepEqual(byte_pad({ value: [0x44332211, 0x00006655], binLen: 48 }, 8), [0x22110801, 0x66554433]);
  });
});

describe("Test resolveCSHAKEOptions", () => {
  const resolveCSHAKEOptions = sha3.__get__("resolveCSHAKEOptions");
  it("With No Input", () => {
    assert.deepEqual(resolveCSHAKEOptions(), {
      funcName: { value: [], binLen: 0 },
      customization: { value: [], binLen: 0 },
    });
  });

  it("With customization Specified", () => {
    assert.deepEqual(resolveCSHAKEOptions({ customization: { value: "00112233", format: "HEX" } }), {
      funcName: { value: [], binLen: 0 },
      customization: { value: [0x33221100], binLen: 32 },
    });
  });

  it("With funcName Specified", () => {
    assert.deepEqual(resolveCSHAKEOptions({ funcName: { value: "00112233", format: "HEX" } }), {
      customization: { value: [], binLen: 0 },
      funcName: { value: [0x33221100], binLen: 32 },
    });
  });
});

describe("Test resolveKMACOptions", () => {
  const resolveKMACOptions = sha3.__get__("resolveKMACOptions");
  it("With No Input", () => {
    assert.throws(() => {
      resolveKMACOptions();
    }, "kmacKey must include a value and format");
  });

  it("With customization Specified", () => {
    assert.deepEqual(
      resolveKMACOptions({
        kmacKey: { value: "44556677", format: "HEX" },
        customization: { value: "00112233", format: "HEX" },
      }),
      {
        funcName: { value: [0x43414d4b], binLen: 32 },
        customization: { value: [0x33221100], binLen: 32 },
        kmacKey: { value: [0x77665544], binLen: 32 },
      },
    );
  });

  it("With funcName Specified", () => {
    assert.deepEqual(
      resolveKMACOptions({
        kmacKey: { value: "44556677", format: "HEX" },
        funcName: { value: "00112233", format: "HEX" },
      }),
      {
        funcName: { value: [0x43414d4b], binLen: 32 },
        customization: { value: [], binLen: 0 },
        kmacKey: { value: [0x77665544], binLen: 32 },
      },
    );
  });
});

describe("Test getNewState", () => {
  it("For All Variants", () => {
    assert.deepEqual(getNewState("SHA3-224"), newState);
  });
});

describe("Test cloneSHA3State", () => {
  const cloneSHA3State = sha3.__get__("cloneSHA3State");

  const state = [
    [new Int_64(0, 1), new Int_64(0, 2), new Int_64(0, 3), new Int_64(0, 4), new Int_64(0, 5)],
    [new Int_64(0, 6), new Int_64(0, 7), new Int_64(0, 8), new Int_64(0, 9), new Int_64(0, 0xa)],
    [new Int_64(0, 0xb), new Int_64(0, 0xc), new Int_64(0, 0xd), new Int_64(0, 0xb), new Int_64(0, 0xf)],
    [new Int_64(0, 0x10), new Int_64(0, 0x11), new Int_64(0, 0x12), new Int_64(0, 0x3), new Int_64(0, 0x14)],
    [new Int_64(0, 0x15), new Int_64(0, 0x16), new Int_64(0, 0x17), new Int_64(0, 0x18), new Int_64(0, 0x19)],
  ];

  it("For All Variants", () => {
    assert.notEqual(cloneSHA3State(state), state);
    assert.deepEqual(cloneSHA3State(state), state);
  });
});

describe("Test roundSHA3", () => {
  it("With NIST Test Inputs", () => {
    assert.deepEqual(sha3.__get__("roundSHA3")(NISTSHA3Round1In.slice(), getNewState()), NISTSHA3Round1Out);
  });
});

describe("Test finalizeSHA3", () => {
  it("With NIST Test Inputs", () => {
    const roundStub = sinon.stub().onCall(0).returns(NISTSHA3Round1Out).onCall(1).returns(NISTSHA3Round2Out);
    sha3.__with__({ roundSHA3: roundStub })(() => {
      assert.deepEqual(
        sha3.__get__("finalizeSHA3")(
          NISTSHA3Round1In.concat(NISTSHA3Round2In),
          1600,
          -1,
          getNewState(),
          1152,
          0x06,
          224,
        ),
        [0x6a817693, 0x723f50ba, 0xebe76cf9, 0x5d09ac65, 0x4bbee3ee, 0xa1c2bbf9, 0xe0117ecb],
      );
    });
  });

  it("With outputLen Greater Than Blocksize", () => {
    // This is emulating SHAKE128 */
    assert.deepEqual(
      sha3.__get__("finalizeSHA3")(
        NISTSHA3Round1In.concat(NISTSHA3Round2In),
        1600,
        -1,
        getNewState(),
        1344,
        0x1f,
        2048,
      ),
      SHAKE128Len2048Out,
    );
  });
});

describe("Test jsSHA(SHA3)", () => {
  const jsSHA = sha3.__get__("jsSHA");
  class jsSHAATest extends jsSHA {
    constructor(variant: VariantNoCSHAKEType, inputFormat: "TEXT", options?: FixedLengthOptionsEncodingType);
    constructor(
      variant: VariantNoCSHAKEType,
      inputFormat: FormatNoTextType,
      options?: FixedLengthOptionsNoEncodingType,
    );
    constructor(variant: "CSHAKE128" | "CSHAKE256", inputFormat: "TEXT", options?: CSHAKEOptionsEncodingType);
    constructor(
      variant: "CSHAKE128" | "CSHAKE256",
      inputFormat: FormatNoTextType,
      options?: CSHAKEOptionsNoEncodingType,
    );
    constructor(variant: "KMAC128" | "KMAC256", inputFormat: "TEXT", options: KMACOptionsEncodingType);
    constructor(variant: "KMAC128" | "KMAC256", inputFormat: FormatNoTextType, options: KMACOptionsNoEncodingType);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    constructor(variant: any, inputFormat: any, options?: any) {
      super(variant, inputFormat, options);
    }

    /*
     * Dirty hack function to expose the protected members of jsSHABase
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    getter(propName: string): any {
      // @ts-ignore - Override "any" ban as this is only used in testing
      return this[propName];
    }
    /*
     * Dirty hack function to expose the protected members of jsSHABase
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    setter(propName: string, value: any): void {
      // @ts-ignore - Override "any" ban as this is only used in testing
      this[propName] = value;
    }
  }

  [
    {
      variant: "SHA3-224",
      outputBinLen: 224,
      variantBlockSize: 1152,
      delimiter: 0x06,
      HMACSupported: true,
      isVariableLen: false,
    },
    {
      variant: "SHA3-256",
      outputBinLen: 256,
      variantBlockSize: 1088,
      delimiter: 0x06,
      HMACSupported: true,
      isVariableLen: false,
    },
    {
      variant: "SHA3-384",
      outputBinLen: 384,
      variantBlockSize: 832,
      delimiter: 0x06,
      HMACSupported: true,
      isVariableLen: false,
    },
    {
      variant: "SHA3-512",
      outputBinLen: 512,
      variantBlockSize: 576,
      delimiter: 0x06,
      HMACSupported: true,
      isVariableLen: false,
    },
    {
      variant: "SHAKE128",
      outputBinLen: -1,
      variantBlockSize: 1344,
      delimiter: 0x1f,
      HMACSupported: false,
      isVariableLen: true,
    },
    {
      variant: "SHAKE256",
      outputBinLen: -1,
      variantBlockSize: 1088,
      delimiter: 0x1f,
      HMACSupported: false,
      isVariableLen: true,
    },
    {
      // Test whether empty customization + function-name "reverts" CSHAKE to SHAKE
      variant: "CSHAKE128",
      outputBinLen: -1,
      variantBlockSize: 1344,
      delimiter: 0x1f,
      isVariableLen: true,
      HMACSupported: false,
      customization: { value: "", format: "TEXT" },
    },
    {
      // Test whether empty customization + function-name "reverts" CSHAKE to SHAKE
      variant: "CSHAKE256",
      outputBinLen: -1,
      variantBlockSize: 1088,
      delimiter: 0x1f,
      isVariableLen: true,
      HMACSupported: false,
      customization: { value: "", format: "TEXT" },
    },
    {
      variant: "CSHAKE128",
      outputBinLen: -1,
      variantBlockSize: 1344,
      delimiter: 0x04,
      isVariableLen: true,
      HMACSupported: false,
      customization: { value: "a", format: "TEXT" },
    },
    {
      variant: "CSHAKE256",
      outputBinLen: -1,
      variantBlockSize: 1088,
      delimiter: 0x04,
      isVariableLen: true,
      HMACSupported: false,
      customization: { value: "a", format: "TEXT" },
    },
    {
      variant: "KMAC128",
      outputBinLen: -1,
      variantBlockSize: 1344,
      delimiter: 0x04,
      isVariableLen: true,
      HMACSupported: false,
      kmacKey: { value: "a", format: "TEXT" },
    },
    {
      variant: "KMAC256",
      outputBinLen: -1,
      variantBlockSize: 1088,
      delimiter: 0x04,
      isVariableLen: true,
      HMACSupported: false,
      kmacKey: { value: "a", format: "TEXT" },
    },
  ].forEach((test) => {
    it(`${test.variant} State Initialization`, () => {
      /*
       * Check a few basic things:
       *   1. All of the variant parameters are correct
       *   2. Calling stateClone function returns a *copy* of the state
       *   3. Calling roundFunc, newStateFunc, and finalizeFunc call the expected functions
       */
      sinon.reset();
      const roundFuncSpy = sinon.spy(),
        finalizeFuncSpy = sinon.spy(),
        newStateFuncSpy = sinon.spy();
      sha3.__with__({ roundSHA3: roundFuncSpy, finalizeSHA3: finalizeFuncSpy, getNewState: newStateFuncSpy })(() => {
        // @ts-ignore
        const hash = new jsSHAATest(test.variant, "HEX", { customization: test.customization, kmacKey: test.kmacKey });

        // Check #1
        assert.equal(hash.getter("bigEndianMod"), 1);
        assert.equal(hash.getter("variantBlockSize"), test.variantBlockSize);
        assert.equal(hash.getter("outputBinLen"), test.outputBinLen);
        assert.equal(hash.getter("isVariableLen"), test.isVariableLen);
        assert.equal(hash.getter("HMACSupported"), test.HMACSupported);

        // Check #2
        const state = [[0xdeadbeef], [0xdeadbeef], [0xdeadbeef], [0xdeadbeef], [0xdeadbeef]];
        const clonedState = hash.getter("stateCloneFunc")(state);
        assert.notEqual(state, clonedState);
        assert.deepEqual(state, clonedState);

        // Check #3
        hash.getter("roundFunc")([0xdeadbeef], [[0xfacefeed]]);
        assert.isTrue(roundFuncSpy.lastCall.calledWithExactly([0xdeadbeef], [[0xfacefeed]]));

        //hash.getter("newStateFunc")(test.variant);
        assert.isTrue(newStateFuncSpy.lastCall.calledWithExactly(test.variant));

        hash.getter("finalizeFunc")([0xdeadbeef], 32, 0, [[0xfacefeed]], test.outputBinLen);
        assert.isTrue(
          finalizeFuncSpy.lastCall.calledWithExactly(
            [0xdeadbeef],
            32,
            0,
            [[0xfacefeed]],
            test.variantBlockSize,
            test.delimiter,
            test.outputBinLen,
          ),
        );
      });
    });
  });

  it("CSHAKE Without Options", () => {
    const hash = new jsSHAATest("CSHAKE128", "HEX");
    /* funcName and customization are both empty so nothing should be processed */
    assert.deepEqual(hash.getter("intermediateState"), newState);
    assert.equal(hash.getter("processedLen"), 0);
  });

  it("CSHAKE With Customization", () => {
    const roundSpy = sinon.spy();
    sha3.__with__({ roundSHA3: roundSpy })(() => {
      const hash = new jsSHAATest("CSHAKE128", "HEX", { customization: { value: "Email Signature", format: "TEXT" } });

      assert.isTrue(roundSpy.calledOnceWithExactly(NISTCSHAKERoundIn, newState.slice()));
      assert.equal(hash.getter("processedLen"), hash.getter("variantBlockSize"));
    });
  });

  it("CSHAKE With function-name", () => {
    const roundSpy = sinon.spy();
    sha3.__with__({ roundSHA3: roundSpy })(() => {
      const hash = new jsSHAATest("CSHAKE128", "HEX", { funcName: { value: "TEST", format: "TEXT" } });

      assert.isTrue(roundSpy.calledOnceWithExactly(CSHAKEWithFuncRoundIn, newState.slice()));
      assert.equal(hash.getter("processedLen"), hash.getter("variantBlockSize"));
    });
  });

  it("KMAC128 With Customization", () => {
    const roundSpy = sinon.spy();
    sha3.__with__({ roundSHA3: roundSpy })(() => {
      const hash = new jsSHAATest("KMAC128", "HEX", {
        customization: { value: "My Tagged Application", format: "TEXT" },
        kmacKey: { value: "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", format: "HEX" },
      });

      assert.isTrue(roundSpy.getCall(0).calledWith(NISTKMACCustomizationRound1In));
      assert.isTrue(roundSpy.getCall(1).calledWith(NISTKMACCustomizationRound2In));
      assert.equal(hash.getter("processedLen"), 2 * hash.getter("variantBlockSize"));
    });
  });

  it("With Invalid Variant", () => {
    // @ts-ignore - Deliberate bad variant value to test exceptions
    assert.throws(() => new jsSHA("SHA-TEST", "HEX"), "Chosen SHA variant is not supported");
  });

  it("CSHAKE With numRounds", () => {
    assert.throws(() => new jsSHA("CSHAKE128", "HEX", { numRounds: 2 }), "Cannot set numRounds for CSHAKE variants");
  });

  it("CSHAKE Without Customization Value", () => {
    assert.throws(
      () => new jsSHA("CSHAKE128", "HEX", { customization: { format: "TEXT" } }),
      "Customization must include a value and format",
    );
  });

  it("CSHAKE Without Customization Format", () => {
    assert.throws(
      () => new jsSHA("CSHAKE128", "HEX", { customization: { value: "abc" } }),
      "Customization must include a value and format",
    );
  });

  it("CSHAKE With funcName Missing format", () => {
    assert.throws(
      () =>
        new jsSHA("CSHAKE128", "HEX", { customization: { value: "abc", format: "TEXT" }, funcName: { value: "A" } }),
      "funcName must include a value and format",
    );
  });

  it("CSHAKE With funcName Missing Value", () => {
    assert.throws(
      () =>
        new jsSHA("CSHAKE128", "HEX", {
          customization: { value: "abc", format: "TEXT" },
          funcName: { format: "TEXT" },
        }),
      "funcName must include a value and format",
    );
  });

  it("KMAC128 With numRounds", () => {
    assert.throws(
      () => new jsSHA("KMAC128", "HEX", { numRounds: 2, kmacKey: { value: "TEST", format: "TEXT" } }),
      "Cannot set numRounds with MAC",
    );
  });

  it("KMAC128 Without kmacKey", () => {
    assert.throws(() => new jsSHA("KMAC128", "HEX"), "kmacKey must include a value and format");
  });

  it("KMAC128 With kmacKey Missing Value", () => {
    assert.throws(
      () => new jsSHA("KMAC128", "HEX", { kmacKey: { format: "HEX" } }),
      "kmacKey must include a value and format",
    );
  });

  it("KMAC128 With kmacKey Missing Format", () => {
    assert.throws(
      () => new jsSHA("KMAC128", "HEX", { kmacKey: { value: "AA" } }),
      "kmacKey must include a value and format",
    );
  });

  it("With hmacKey Set at Instantiation", () => {
    const hash = new jsSHAATest("SHA3-256", "HEX", { hmacKey: { value: "TEST", format: "TEXT" } });
    assert.isTrue(hash.getter("macKeySet"));
  });

  it("With hmacKey Set at Instantiation but then also setHMACKey", () => {
    const hash = new jsSHAATest("SHA3-256", "HEX", { hmacKey: { value: "TEST", format: "TEXT" } });
    assert.throws(() => {
      hash.setHMACKey("TEST", "TEXT");
    }, "MAC key already set");
  });
});

runHashTests("SHA3-224", sha3.__get__("jsSHA"));
runHashTests("SHA3-256", sha3.__get__("jsSHA"));
runHashTests("SHA3-384", sha3.__get__("jsSHA"));
runHashTests("SHA3-512", sha3.__get__("jsSHA"));
runHashTests("SHAKE128", sha3.__get__("jsSHA"));
runHashTests("SHAKE256", sha3.__get__("jsSHA"));
runHashTests("CSHAKE128", sha3.__get__("jsSHA"));
runHashTests("CSHAKE256", sha3.__get__("jsSHA"));
runHashTests("KMAC128", sha3.__get__("jsSHA"));
runHashTests("KMAC256", sha3.__get__("jsSHA"));

[evidence record sha256:bef194b2bcb16ccd5b03888bac02ae4edd2d1a943d0527d5f5dab3759d94240e kind tool-call:read]
step 12: calling local:qwen3.6:35b-a3b
model error (retrying): the model spent all 8192 output tokens without emitting text or a tool call
model error (retrying): Response stream ended without a finish reason.
stopped: output-cap after 12 steps, 261277 tokens
gate typecheck not-applicable: package.json declares no typecheck script [evidence record sha256:2937696362f6458fe282589c221bc4c8fee1e95f3a2fcdf7bdda33a9c257023a]
gate lint not-applicable: package.json declares no lint script [evidence record sha256:80f9d94a737526e9c5c413a32d943f4febe0196f251ff0d5ebdfb62b7df46ec0]
gate format not-applicable: package.json declares no check-only format script, and running a writing formatter as a gate would edit the tree it is judging [evidence record sha256:9b21ee9385ec49965029e8ae457e7fa57f61ec2cc75ef84219ddfa2809af47cb]
gate tests failed: the command exited 30 [evidence record sha256:28b9136f63955d4b325e7ebd30f9bd93bcc49436ac5bf36d96c152b6727167ab]
gate file-set passed: nothing changed and no file set was declared, so there is nothing to check [evidence record sha256:da941441683d2a932e3656d29e47bf1d3dd8e8827a4fa28340e9192147fc0140]
gate placeholder passed: no placeholder marker was introduced by this change [evidence record sha256:04cbfbf05767e77d5767fb5772e9a7e62cec4ae74d421e013fe257c5eccb892c]
gate secret-scan passed: no known credential pattern appears in the added lines [evidence record sha256:1205c218e3298f2b2a033cfedf23b7ee3ea0c6b62794e4df970e745e5f7a0cb4]
gate behaviour-probe passed: 0 changed function(s) still answer to their inputs. [evidence record sha256:6c58dc822ecce8f4da2f5c78e5027adb86eef9574d10c896a93cc27d285aa5df]
gate diff-budget passed (advisory): within budget: 0 file(s) and 0 added line(s) [evidence record sha256:e3dbe12b4205d31760f1fad23748c5ea393fa58eb135b7d0aec249a7ae178e24]
auto-resolve attempt 1 of 2
step 1: calling local:qwen3.6:35b-a3b
plan: Looking at the test output, the failures all relate to `packedLEConcat` producing wrong packed arrays (signed vs unsigned int32 confusion), which cascades into `encode_string`, `byte_pad`, CSHAKE, and KMAC tests. Let me examine the source code.
tool shell <- {"command":"cd /work && cat src/sha3.ts | grep -n \"packedLEConcat\" | head -20"}
tool shell failed: denied: "cd /work && cat src/sha3.ts | grep -n "packedLEConcat" | head -20" is not on the shell allowlist. Confirmation was declined.
[evidence record sha256:9d4a40ba9e24282895f7902de315816d576fdd515be8ce1eb77f37ed995f49e7 kind tool-call:shell]
tool shell <- {"command":"cd /work && cat test/src/test_common.ts | grep -n \"packedLEConcat\" -A 5 | head -60"}
tool shell failed: denied: "cd /work && cat test/src/test_common.ts | grep -n "packedLEConcat" -A 5 | head -60" is not on the shell allowlist. Confirmation was declined.
[evidence record sha256:688ebcaa3a351f6e78e251ec721e30f3c4f821dc6e7f5b42017d77e16f541cf5 kind tool-call:shell]
step 2: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"ls /work/src/"}
tool shell ok: exit code: 0
stdout:
common.ts
converters.ts
custom_types.ts
license_header.es3.txt
license_header.es6.txt
primitives_32.ts
primitives_64.ts
sha.ts
sha1.ts
sha256.ts
sha3.ts
sha512.ts
[evidence record sha256:858ca0ee4f44abf57c7094ac000f7628ece3e05ffa49c2ae6396ba6ab3535f6f kind tool-call:shell]
tool shell <- {"command":"ls /work/test/src/"}
tool shell ok: exit code: 0
stdout:
common.ts
test_common.ts
test_converters.ts
test_primitives_32.ts
test_primitives_64.ts
test_sha.ts
test_sha1.ts
test_sha256.ts
test_sha3.ts
test_sha3_consts.ts
test_sha512.ts
[evidence record sha256:1312a5f306aa5977acef0ce38c6b7eaaf442dbf95c1479468743b431c4575757 kind tool-call:shell]
step 3: calling local:qwen3.6:35b-a3b
tool read <- {"path":"work/test/src/test_common.ts"}
tool read failed: failed: ENOENT: no such file or directory, open '/work/work/test/src/test_common.ts', caused by ENOENT
[evidence record sha256:fe3884590329e3237fad44a62cee3caab21f4cc3db925e831224a8b0cc254db7 kind tool-call:read]
step 4: calling local:qwen3.6:35b-a3b
tool list <- {"path":"test/src"}
tool list ok: common.ts
test_common.ts
test_converters.ts
test_primitives_32.ts
test_primitives_64.ts
test_sha.ts
test_sha1.ts
test_sha256.ts
test_sha3.ts
test_sha3_consts.ts
test_sha512.ts
[evidence record sha256:cc4b4e6a09c8390cdfa48dbf2279fab2071e5ed9de2cf94d96ed621af7176a5e kind tool-call:list]
tool list <- {"path":"src"}
tool list ok: common.ts
converters.ts
custom_types.ts
license_header.es3.txt
license_header.es6.txt
primitives_32.ts
primitives_64.ts
sha.ts
sha1.ts
sha256.ts
sha3.ts
sha512.ts
[evidence record sha256:3e6781fb7453b5a58ca7247e57c28c39af059255443407bc13222a0f9336b661 kind tool-call:list]
step 5: calling local:qwen3.6:35b-a3b
tool read <- {"path":"test/src/test_common.ts"}
tool read ok: import { describe, it } from "mocha";
import sinon from "sinon";
import { assert } from "chai";
import { getOutputOpts, parseInputOption, packedLEConcat, jsSHABase } from "../../src/common";
import {
  FixedLengthOptionsEncodingType,
  FixedLengthOptionsNoEncodingType,
  FormatNoTextType,
  packedValue,
} from "../../src/custom_types";

describe("Test packedLEConcat", () => {
  it("For 2 0-byte Values", () => {
    assert.deepEqual(packedLEConcat({ value: [], binLen: 0 }, { value: [], binLen: 0 }), { value: [], binLen: 0 });
  });

  it("For 2 3-byte Values", () => {
    assert.deepEqual(packedLEConcat({ value: [0x00112233], binLen: 24 }, { value: [0x00aabbcc], binLen: 24 }), {
      value: [0xcc112233 | 0, 0x0000aabb],
      binLen: 48,
    });
  });

  it("For 2 4-byte Values", () => {
    assert.deepEqual(packedLEConcat({ value: [0x11223344], binLen: 32 }, { value: [0xaabbccdd], binLen: 32 }), {
      value: [0x11223344, 0xaabbccdd],
      binLen: 64,
    });
  });

  it("For 1 1-byte and 1 3-byte Value", () => {
    assert.deepEqual(packedLEConcat({ value: [0x00000011], binLen: 8 }, { value: [0x00aabbcc], binLen: 24 }), {
      value: [0xaabbcc11 | 0],
      binLen: 32,
    });
  });
});

describe("Test parseInputOption", () => {
  it("For Fully Specified Value", () => {
    assert.deepEqual(parseInputOption("kmacKey", { value: "00112233", format: "HEX" }, 1), {
      value: [0x33221100],
      binLen: 32,
    });
  });

  it("For Empty but Optional Value", () => {
    assert.deepEqual(parseInputOption("kmacKey", undefined, 1, { value: [], binLen: 0 }), { value: [], binLen: 0 });
  });

  it("For Empty but Required Value", () => {
    assert.throws(() => {
      parseInputOption("kmacKey", undefined, 1);
    }, "kmacKey must include a value and format");
  });

  it("For Value Missing value Key", () => {
    assert.throws(() => {
      // @ts-ignore - Deliberately bad value for test
      parseInputOption("kmacKey", { format: "HEX" }, 1);
    }, "kmacKey must include a value and format");
  });

  it("For Value Missing binLen Key", () => {
    assert.throws(() => {
      // @ts-ignore - Deliberately bad value for test
      parseInputOption("kmacKey", { value: "TEST" }, 1);
    }, "kmacKey must include a value and format");
  });
});

describe("Test getOutputOpts", () => {
  it("Empty Input", () => {
    assert.deepEqual(getOutputOpts(), { outputUpper: false, b64Pad: "=", outputLen: -1 });
  });

  it("b64Pad Specified", () => {
    assert.deepEqual(getOutputOpts({ b64Pad: "#" }), { outputUpper: false, b64Pad: "#", outputLen: -1 });
  });

  it("outputLen Specified", () => {
    assert.deepEqual(getOutputOpts({ outputLen: 16, shakeLen: 8 }), { outputUpper: false, b64Pad: "=", outputLen: 16 });
  });

  it("shakeLen Specified", () => {
    assert.deepEqual(getOutputOpts({ shakeLen: 8 }), { outputUpper: false, b64Pad: "=", outputLen: 8 });
  });

  it("Invalid shakeLen", () => {
    assert.throws(() => {
      getOutputOpts({ shakeLen: 1 });
    }, "Output length must be a multiple of 8");
  });

  it("Invalid outputLen", () => {
    assert.throws(() => {
      getOutputOpts({ outputLen: 1 });
    }, "Output length must be a multiple of 8");
  });

  it("Invalid b64Pad", () => {
    assert.throws(() => {
      // @ts-ignore - Deliberate bad b64Pad value to test exceptions
      getOutputOpts({ b64Pad: 1 });
    }, "Invalid b64Pad formatting option");
  });

  it("Invalid outputUpper", () => {
    assert.throws(() => {
      // @ts-ignore - Deliberate bad outputUpper value to test exceptions
      getOutputOpts({ outputUpper: 1 });
    }, "Invalid outputUpper formatting option");
  });
});

describe("Test jsSHABase", () => {
  const stubbedStrConverter = sinon.stub(),
    stubbedRound = sinon.stub(),
    stubbedNewState = sinon.stub(),
    stubbedFinalize = sinon.stub(),
    stubbedStateClone = sinon.stub(),
    dummyVals = [
      0x11223344, 0xaabbccdd, 0xdeadbeef, 0xfacefeed, 0xbaddcafe, 0xdeadcafe, 0xdead2bad, 0xdeaddead, 0xcafed00d,
      0xdecafbad, 0xfee1dead, 0xdeadfa11,
    ];

  class jsSHAATest extends jsSHABase<number[], "SHA-TEST"> {
    intermediateState: number[];
    variantBlockSize: number;
    bigEndianMod: -1 | 1;
    outputBinLen: number;
    isVariableLen: boolean;
    HMACSupported: boolean;

    /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
    converterFunc: (input: any, existingBin: number[], existingBinLen: number) => packedValue;
    roundFunc: (block: number[], H: number[]) => number[];
    finalizeFunc: (remainder: number[], remainderBinLen: number, processedBinLen: number, H: number[]) => number[];
    stateCloneFunc: (state: number[]) => number[];
    newStateFunc: (variant: "SHA-TEST") => number[];
    getMAC: () => number[];

    constructor(variant: "SHA-TEST", inputFormat: "TEXT", options?: FixedLengthOptionsEncodingType);
    constructor(variant: "SHA-TEST", inputFormat: FormatNoTextType, options?: FixedLengthOptionsNoEncodingType);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    constructor(variant: any, inputFormat: any, options?: any) {
      super(variant, inputFormat, options);

      this.bigEndianMod = -1;
      this.converterFunc = stubbedStrConverter;
      this.roundFunc = stubbedRound;
      this.stateCloneFunc = stubbedStateClone;
      this.newStateFunc = stubbedNewState;
      this.finalizeFunc = stubbedFinalize;
      // eslint-disable-next-line @typescript-eslint/unbound-method
      this.getMAC = this._getHMAC;

      this.intermediateState = [0, 0];
      this.variantBlockSize = 64;
      this.outputBinLen = 64;
      this.isVariableLen = false;
      this.HMACSupported = true;
    }

    /*
     * Dirty hack function to expose the protected members of jsSHABase
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    getter(propName: string): any {
      // @ts-ignore - Override "any" ban as this is only used in testing
      return this[propName];
    }

    /*
     * Dirty hack function to expose the protected members of jsSHABase
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    setter(propName: string, value: any): void {
      // @ts-ignore - Override "any" ban as this is only used in testing
      this[propName] = value;
    }
  }

  it("Test Constructor with Empty Options", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");

    assert.equal(stubbedJsSHA.getter("inputFormat"), "HEX");
    assert.equal(stubbedJsSHA.getter("utfType"), "UTF8");
    assert.equal(stubbedJsSHA.getter("shaVariant"), "SHA-TEST");
    assert.equal(stubbedJsSHA.getter("numRounds"), 1);
    assert.equal(stubbedJsSHA.getter("remainderLen"), 0);
    assert.equal(stubbedJsSHA.getter("processedLen"), 0);
    assert.isFalse(stubbedJsSHA.getter("updateCalled"));
    assert.isFalse(stubbedJsSHA.getter("macKeySet"));
    assert.deepEqual(stubbedJsSHA.getter("remainder"), []);
    assert.deepEqual(stubbedJsSHA.getter("keyWithIPad"), []);
    assert.deepEqual(stubbedJsSHA.getter("keyWithOPad"), []);
  });

  it("Test Constructor with Bad numRounds", () => {
    assert.throws(() => {
      new jsSHAATest("SHA-TEST", "HEX", { numRounds: 1.2 });
    }, "numRounds must a integer >= 1");

    assert.throws(() => {
      new jsSHAATest("SHA-TEST", "HEX", { numRounds: -1 });
    }, "numRounds must a integer >= 1");
  });

  it("Test update", () => {
    /*
     * This is rather difficult to test so we want to check a few basic things:
     *   1. It returns a reference to the jsSHA object
     *   2. It passed the input to the string conversion function correctly
     *   3. It did *not* call the round function when the input was smaller than the block size
     *   4. Intermediate state was untouched but remainder variables are updated
     *   5. It *did* call the round function when the input was greater than or equal to than the block size
     *   6. Intermediate state and associated variables are set correctly
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX"),
      inputStr = "ABCD";
    sinon.reset();

    stubbedStrConverter
      .onFirstCall()
      .returns({ value: [dummyVals[0]], binLen: 32 })
      .onSecondCall()
      .returns({ value: [dummyVals[0], dummyVals[0]], binLen: 64 });
    stubbedRound.returns([dummyVals[1], dummyVals[2]]);

    // Check #1
    assert.equal(stubbedJsSHA, stubbedJsSHA.update(inputStr));
    // Check #2
    assert.isTrue(stubbedStrConverter.calledOnceWith(inputStr, [], 0));
    // Check #3
    assert.isFalse(stubbedRound.called);
    // Check #4
    assert.deepEqual(stubbedJsSHA.getter("intermediateState"), [0, 0]);
    assert.deepEqual(stubbedJsSHA.getter("remainder"), [dummyVals[0]]);
    assert.equal(stubbedJsSHA.getter("remainderLen"), 32);
    assert.equal(stubbedJsSHA.getter("processedLen"), 0);
    assert.isTrue(stubbedJsSHA.getter("updateCalled"));

    stubbedJsSHA.update(inputStr);
    // Check #2 again to make sure state is being passed correctly
    assert.equal(stubbedStrConverter.callCount, 2);
    assert.isTrue(stubbedStrConverter.getCall(1).calledWithExactly(inputStr, [dummyVals[0]], 32));
    // Check #5
    assert.isTrue(stubbedRound.calledOnceWith([dummyVals[0], dummyVals[0]], [0, 0]));

    // Check #6
    assert.deepEqual(stubbedJsSHA.getter("intermediateState"), [dummyVals[1], dummyVals[2]]);
    assert.deepEqual(stubbedJsSHA.getter("remainder"), []);
    assert.equal(stubbedJsSHA.getter("remainderLen"), 0);
    assert.equal(stubbedJsSHA.getter("processedLen"), 64);
  });

  it("Test getHash Without Needed outputLen ", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");

    stubbedJsSHA.setter("isVariableLen", true);
    assert.throws(() => {
      stubbedJsSHA.getHash("HEX", {});
    }, "Output length must be specified in options");
  });

  it("Test getHash", () => {
    /*
     * Check a few basic things:
     *   1. The output of getHash should equal the first outputBinLen bits of the output of finalizeFunc
     *   2. intermediateState and remainder should not be changed by calling getHash
     *   3. finalize should be called once with the correct inputs
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    sinon.reset();
    stubbedFinalize.returns([dummyVals[0], dummyVals[1]]);
    stubbedStateClone.returns([dummyVals[2], dummyVals[3]]);

    stubbedJsSHA.setter("intermediateState", [dummyVals[4]]);
    const intermediateState = stubbedJsSHA.getter("intermediateState");
    stubbedJsSHA.setter("remainder", [dummyVals[5]]);
    const remainder = stubbedJsSHA.getter("remainder");
    stubbedJsSHA.setter("remainderLen", 32);
    stubbedJsSHA.setter("processedLen", 64);

    // Check #1
    assert.equal(stubbedJsSHA.getHash("HEX"), dummyVals[0].toString(16) + dummyVals[1].toString(16));

    // Check #2, note deliberate use of equal vs deepEqual
    assert.equal(intermediateState, stubbedJsSHA.getter("intermediateState"));
    assert.equal(remainder, stubbedJsSHA.getter("remainder"));

    // Check #3
    assert.isTrue(
      stubbedFinalize.calledOnceWith(
        [dummyVals[5]],
        32,
        64,
        [dummyVals[2], dummyVals[3]],
        stubbedJsSHA.getter("outputBinLen"),
      ),
    );
  });

  it("Test getHash for SHAKE", () => {
    /*
     * Check a few basic things:
     *   1. The output of getHash should equal the first outputLen bits of the output of finalizeFunc
     *   2. finalize should be called once with the correct inputs
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    stubbedJsSHA.setter("isVariableLen", true);
    sinon.reset();
    stubbedFinalize.returns([dummyVals[0], dummyVals[1]]);
    stubbedStateClone.returns([dummyVals[2], dummyVals[3]]);

    stubbedJsSHA.setter("intermediateState", [dummyVals[4]]);
    stubbedJsSHA.setter("remainder", [dummyVals[5]]);
    stubbedJsSHA.setter("remainderLen", 32);
    stubbedJsSHA.setter("processedLen", 64);

    // Check #1
    assert.equal(stubbedJsSHA.getHash("HEX", { outputLen: 32 }), dummyVals[0].toString(16));

    // Check #2
    assert.isTrue(stubbedFinalize.calledOnceWith([dummyVals[5]], 32, 64, [dummyVals[2], dummyVals[3]], 32));
  });

  it("Test getHash for numRounds=3", () => {
    /*
     * Check a few basic things:
     *   1. The output of getHash should equal the output of last finalizeFunc call
     *   2. finalizeFunc should be called numRound times
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX", { numRounds: 3 });
    sinon.reset();
    stubbedFinalize.returns([dummyVals[0], dummyVals[1]]).onCall(2).returns([dummyVals[2], dummyVals[3]]);

    // Check #1
    assert.equal(stubbedJsSHA.getHash("HEX"), dummyVals[2].toString(16) + dummyVals[3].toString(16));

    // Check #2
    assert.equal(stubbedFinalize.callCount, 3);
  });

  it("Test getHash for SHAKE numRounds=3", () => {
    /*
     * Check a few basic things:
     *   1. The output of getHash should equal the output of last finalizeFunc call
     *   2. finalizeFunc should be called numRound times
     *   3. The last numRound-1 calls of finalizeFunc should have the last 32-outputLen bits 0ed out
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX", { numRounds: 3 });
    stubbedJsSHA.setter("isVariableLen", true);
    sinon.reset();
    stubbedFinalize.returns([dummyVals[0], dummyVals[1]]).onCall(2).returns([dummyVals[2], dummyVals[3]]);

    // Check #1
    assert.equal(stubbedJsSHA.getHash("HEX", { outputLen: 24 }), dummyVals[2].toString(16).substr(0, 6));

    // Check #2
    assert.equal(stubbedFinalize.callCount, 3);

    // Check #3
    stubbedFinalize.getCall(1).calledWith([dummyVals[0], dummyVals[1] & 0x00ffffff], 24);
    stubbedFinalize.getCall(2).calledWith([dummyVals[0], dummyVals[1] & 0x00ffffff], 24);
  });

  it("Test setHMACKey with Short Key", () => {
    /*
     * Check a few basic things:
     *   1. keyWithIPad is set correctly
     *   2. keyWithOPad is set correctly
     *   3. The round function was called and its return value stored as intermediateState
     *   4. macKeySet was set
     *   5. processedLen was updated
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    sinon.reset();
    stubbedRound.returns([dummyVals[0], dummyVals[1]]);
    stubbedJsSHA.setHMACKey("ABCD", "TEXT");

    // Check #1
    assert.deepEqual(
      stubbedJsSHA.getter("keyWithIPad"),
      [0x41424344, 0].map((val) => {
        return val ^ 0x36363636;
      }),
    );

    // Check #2
    assert.deepEqual(
      stubbedJsSHA.getter("keyWithOPad"),
      [0x41424344, 0].map((val) => {
        return val ^ 0x5c5c5c5c;
      }),
    );

    // Check #3
    assert.isTrue(
      stubbedRound.calledOnceWithExactly(
        [0x41424344, 0].map((val) => {
          return val ^ 0x36363636;
        }),
        [0, 0],
      ),
    );
    assert.deepEqual(stubbedJsSHA.getter("intermediateState"), [dummyVals[0], dummyVals[1]]);

    // Check #4
    assert.isTrue(stubbedJsSHA.getter("macKeySet"));

    // Check #5
    assert.equal(stubbedJsSHA.getter("processedLen"), stubbedJsSHA.getter("variantBlockSize"));
  });

  it("Test setHMACKey with Long Key", () => {
    /*
     * Check a few basic things:
     *   1. Finalize was called with the correct keying material
     *   2. keyWithIPad is set correctly
     *   3. keyWithOPad is set correctly
     *   4. The round function was called with the input set as the output from finalize and its return value stored as intermediateState
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX"),
      inputStr = "ABCDEFGHABCD",
      inputStrPacked = [0x41424344, 0x45464748, 0x41424344];
    sinon.reset();
    stubbedFinalize.returns([dummyVals[0], dummyVals[1]]);
    stubbedRound.returns([dummyVals[2], dummyVals[3]]);
    stubbedNewState.returns([dummyVals[4], dummyVals[5]]);

    // Need to call setHMACKey with more than 64-bits of key material to test handling of "large" key sizes
    stubbedJsSHA.setHMACKey(inputStr, "TEXT");

    // Check #1
    assert.isTrue(
      stubbedFinalize.calledOnceWithExactly(
        inputStrPacked,
        96,
        0,
        [dummyVals[4], dummyVals[5]],
        stubbedJsSHA.getter("outputBinLen"),
      ),
    );

    // Check #2
    assert.deepEqual(
      stubbedJsSHA.getter("keyWithIPad"),
      [dummyVals[0], dummyVals[1]].map((val) => {
        return val ^ 0x36363636;
      }),
    );

    // Check #3
    assert.deepEqual(
      stubbedJsSHA.getter("keyWithOPad"),
      [dummyVals[0], dummyVals[1]].map((val) => {
        return val ^ 0x5c5c5c5c;
      }),
    );

    // Check #4
    assert.isTrue(
      stubbedRound.calledOnceWithExactly(
        [dummyVals[0], dummyVals[1]].map((val) => {
          return val ^ 0x36363636;
        }),
        [0, 0],
      ),
    );
    assert.deepEqual(stubbedJsSHA.getter("intermediateState"), [dummyVals[2], dummyVals[3]]);
  });

  it("Test setHMACKey Error on Double Call", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    stubbedJsSHA.setter("macKeySet", true);
    sinon.reset();

    assert.throws(() => {
      stubbedJsSHA.setHMACKey("ABCD", "TEXT");
    }, "MAC key already set");
  });

  it("Test setHMACKey Error on numRounds > 1", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX", { numRounds: 2 });
    sinon.reset();

    assert.throws(() => {
      stubbedJsSHA.setHMACKey("ABCD", "TEXT");
    }, "Cannot set numRounds with MAC");
  });

  it("Test setHMACKey Error on After update", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    stubbedJsSHA.setter("updateCalled", true);
    sinon.reset();

    assert.throws(() => {
      stubbedJsSHA.setHMACKey("ABCD", "TEXT");
    }, "Cannot set MAC key after calling update");
  });

  it("Test setHMACKey Error on Unsupported Variant", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    stubbedJsSHA.setter("HMACSupported", false);
    sinon.reset();

    assert.throws(() => {
      stubbedJsSHA.setHMACKey("ABCD", "TEXT");
    }, "Variant does not support HMAC");
  });

  it("Test HMAC Return", () => {
    /*
     * Check a few basic things:
     *   1. It returns the formatted output of the last finalizeFunc call
     *   2. finalizeFunc was called with a clone of the remainder and correct parameters
     *   3. roundFunc was called with keyWithOPad
     *   4. finalizeFunc was called with the output of the previous finalizeFunc's output and the roundFunc's state
     *   5. remainder, intermediateState, and remainderLen remain untouched
     *   6. A call to getHash actually returns the HMAC
     */
    sinon.reset();
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX"),
      intermediateState = [dummyVals[6], dummyVals[7]],
      remainder = [dummyVals[0]],
      keyWithOPad = [dummyVals[10], dummyVals[11]],
      newState = [dummyVals[8], dummyVals[9]],
      clonedState = [dummyVals[6], dummyVals[7]],
      getMACStub = sinon.stub().returns([[dummyVals[2]], dummyVals[3]]);

    stubbedFinalize
      .onCall(0)
      .returns([dummyVals[0], dummyVals[1]])
      .onCall(1)
      .returns([[dummyVals[2]], dummyVals[3]]);
    stubbedRound.returns([dummyVals[4], dummyVals[5]]);
    stubbedStateClone.returns(clonedState);
    stubbedNewState.returns(newState);

    stubbedJsSHA.setter("macKeySet", true);
    stubbedJsSHA.setter("processedLen", 64);
    stubbedJsSHA.setter("keyWithOPad", keyWithOPad);
    stubbedJsSHA.setter("remainder", remainder);
    stubbedJsSHA.setter("remainderLen", 32);
    stubbedJsSHA.setter("intermediateState", intermediateState);

    // Check #1
    assert.equal(stubbedJsSHA.getHMAC("HEX"), "deadbeeffacefeed");

    // Check #2
    stubbedFinalize
      .getCall(0)
      .calledWithExactly(
        remainder,
        32,
        stubbedJsSHA.getter("outputBinLen"),
        clonedState,
        stubbedJsSHA.getter("outputBinLen"),
      );

    // Check #3
    stubbedRound.calledOnceWithExactly(keyWithOPad, newState);

    // Check #4
    stubbedFinalize
      .getCall(1)
      .calledWithExactly(
        [dummyVals[0], dummyVals[1]],
        stubbedJsSHA.getter("outputBinLen"),
        stubbedJsSHA.getter("variantBlockSize"),
        [dummyVals[4], dummyVals[5]],
        stubbedJsSHA.getter("outputBinLen"),
      );

    // Check #5
    assert.equal(stubbedJsSHA.getter("remainder"), remainder);
    assert.equal(stubbedJsSHA.getter("remainderLen"), 32);
    assert.equal(stubbedJsSHA.getter("intermediateState"), intermediateState);

    // Check #6
    stubbedJsSHA.setter("getMAC", getMACStub);
    stubbedJsSHA.getHash("HEX");
    assert.equal(getMACStub.callCount, 1);
  });

  it("Test getHMAC Error on Not Setting MAC Key", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    sinon.reset();

    assert.throws(() => {
      stubbedJsSHA.getHMAC("HEX");
    }, "Cannot call getHMAC without first setting MAC key");
  });
});

[evidence record sha256:5ba0f271f3c13c6b391af0a60c0ec2b1dadfe433f7825d16d25e7bc4216b8455 kind tool-call:read]
step 6: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/common.ts"}
tool read ok: import { getStrConverter, getOutputConverter } from "./converters";

import {
  FormatType,
  EncodingType,
  FixedLengthOptionsEncodingType,
  FixedLengthOptionsNoEncodingType,
  FormatNoTextType,
  packedValue,
  GenericInputType,
} from "./custom_types";

export const TWO_PWR_32 = 4294967296;

/* Constant used in SHA-2 families */
export const K_sha2 = [
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98,
  0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
  0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8,
  0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
  0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
  0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
  0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
  0xc67178f2,
];

/* Constant used in SHA-2 families */
export const H_trunc = [0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4];

/* Constant used in SHA-2 families */
export const H_full = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];

export const sha_variant_error = "Chosen SHA variant is not supported";
export const mac_rounds_error = "Cannot set numRounds with MAC";

/**
 * Concatenates 2 packed arrays. Clobbers array `a`.
 *
 * @param a First array to concatenate.
 * @param b Second array to concatenate.
 * @returns The concatentation of `a` + `b`.
 */
export function packedLEConcat(a: packedValue, b: packedValue): packedValue {
  let i, arrOffset;
  const aByteLen = a["binLen"] >>> 3,
    bByteLen = b["binLen"] >>> 3,
    leftShiftAmount = aByteLen << 3,
    rightShiftAmount = (4 - aByteLen) << 3;

  /* If a only contains "full" integers, we can just use concat which is so much easier */
  if (aByteLen % 4 === 0) {
    for (i = 0; i < bByteLen; i += 4) {
      arrOffset = (aByteLen + i) >>> 2;
      /* Left shift chops off bits over 32-bits */
      a["value"][arrOffset] |= b["value"][i >>> 2] << leftShiftAmount;
      a["value"].push(0);
      a["value"][arrOffset + 1] |= b["value"][i >>> 2] >>> rightShiftAmount;
    }

    /* Since an unconditional push was performed above, we may have pushed an extra value if it could have been
       encoded without it.  Check if popping an int off (reducing total length by 4 bytes) is still bigger than the
       needed size. */
    if ((a["value"].length << 2) - 4 >= bByteLen + aByteLen) {
      a["value"].pop();
    }

    return { value: a["value"], binLen: a["binLen"] + b["binLen"] };
  } else {
    return { value: a["value"].concat(b["value"]), binLen: a["binLen"] + b["binLen"] };
  }
}

/**
 * Validate hash list containing output formatting options, ensuring presence of every option or adding the default
 * value.
 *
 * @param options Hashmap of output formatting options from user.
 * @returns Validated hashmap containing output formatting options.
 */
export function getOutputOpts(options?: {
  outputUpper?: boolean;
  b64Pad?: string;
  shakeLen?: number;
  outputLen?: number;
}): { outputUpper: boolean; b64Pad: string; outputLen: number } {
  const retVal = { outputUpper: false, b64Pad: "=", outputLen: -1 },
    outputOptions: { outputUpper?: boolean; b64Pad?: string; shakeLen?: number; outputLen?: number } = options || {},
    lenErrstr = "Output length must be a multiple of 8";

  retVal["outputUpper"] = outputOptions["outputUpper"] || false;

  if (outputOptions["b64Pad"]) {
    retVal["b64Pad"] = outputOptions["b64Pad"];
  }

  if (outputOptions["outputLen"]) {
    if (outputOptions["outputLen"] % 8 !== 0) {
      throw new Error(lenErrstr);
    }
    retVal["outputLen"] = outputOptions["outputLen"];
  } else if (outputOptions["shakeLen"]) {
    if (outputOptions["shakeLen"] % 8 !== 0) {
      throw new Error(lenErrstr);
    }
    retVal["outputLen"] = outputOptions["shakeLen"];
  }

  if ("boolean" !== typeof retVal["outputUpper"]) {
    throw new Error("Invalid outputUpper formatting option");
  }

  if ("string" !== typeof retVal["b64Pad"]) {
    throw new Error("Invalid b64Pad formatting option");
  }

  return retVal;
}

/**
 * Parses an external constructor object and returns a packed number, if possible.
 *
 * @param key The human-friendly key name to prefix any errors with
 * @param value The input value object to parse
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @param fallback Fallback value if `value` is undefined.  If not present and `value` is undefined, an Error is thrown
 */
export function parseInputOption(
  key: string,
  value: GenericInputType | undefined,
  bigEndianMod: -1 | 1,
  fallback?: packedValue,
): packedValue {
  const errStr = key + " must include a value and format";
  if (!value) {
    if (!fallback) {
      throw new Error(errStr);
    }
    return fallback;
  }

  if (typeof value["value"] === "undefined" || !value["format"]) {
    throw new Error(errStr);
  }

  return getStrConverter(
    value["format"],
    // @ts-ignore - the value of encoding gets value checked by getStrConverter
    value["encoding"] || "UTF8",
    bigEndianMod,
  )(value["value"]);
}

export abstract class jsSHABase<StateT, VariantT> {
  /**
   * @param variant The desired SHA variant.
   * @param inputFormat The input format to be used in future `update` calls.
   * @param options Hashmap of extra input options.
   */
  /* Needed inputs */
  protected readonly shaVariant: VariantT;
  protected readonly inputFormat: FormatType;
  protected readonly utfType: EncodingType;
  protected readonly numRounds: number;

  /* State */
  protected abstract intermediateState: StateT;
  protected keyWithIPad: number[];
  protected keyWithOPad: number[];
  protected remainder: number[];
  protected remainderLen: number;
  protected updateCalled: boolean;
  protected processedLen: number;
  protected macKeySet: boolean;

  /* Variant specifics */
  protected abstract readonly variantBlockSize: number;
  protected abstract readonly bigEndianMod: -1 | 1;
  protected abstract readonly outputBinLen: number;
  protected abstract readonly isVariableLen: boolean;
  protected abstract readonly HMACSupported: boolean;

  /* Functions */
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  protected abstract readonly converterFunc: (input: any, existingBin: number[], existingBinLen: number) => packedValue;
  protected abstract readonly roundFunc: (block: number[], H: StateT) => StateT;
  protected abstract readonly finalizeFunc: (
    remainder: number[],
    remainderBinLen: number,
    processedBinLen: number,
    H: StateT,
    outputLen: number,
  ) => number[];
  protected abstract readonly stateCloneFunc: (state: StateT) => StateT;
  protected abstract readonly newStateFunc: (variant: VariantT) => StateT;
  protected abstract readonly getMAC: ((options: { outputLen: number }) => number[]) | null;

  protected constructor(variant: VariantT, inputFormat: "TEXT", options?: FixedLengthOptionsEncodingType);
  protected constructor(variant: VariantT, inputFormat: FormatNoTextType, options?: FixedLengthOptionsNoEncodingType);
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  protected constructor(variant: any, inputFormat: any, options?: any) {
    const inputOptions = options || {};
    this.inputFormat = inputFormat;

    this.utfType = inputOptions["encoding"] || "UTF8";
    this.numRounds = inputOptions["numRounds"] || 1;

    // @ts-ignore - The spec actually says ToString is called on the first parseInt argument so it's OK to use it here
    // to check if an arugment is an integer. This cheat would break if it's used to get the value of the argument.
    if (isNaN(this.numRounds) || this.numRounds !== parseInt(this.numRounds, 10) || 1 > this.numRounds) {
      throw new Error("numRounds must a integer >= 1");
    }

    this.shaVariant = variant;
    this.remainder = [];
    this.remainderLen = 0;
    this.updateCalled = false;
    this.processedLen = 0;
    this.macKeySet = false;
    this.keyWithIPad = [];
    this.keyWithOPad = [];
  }

  /**
   * Hashes as many blocks as possible.  Stores the rest for either a future update or getHash call.
   *
   * @param srcString The input to be hashed.
   * @returns A reference to the object.
   */
  update(srcString: string | ArrayBuffer | Uint8Array): this {
    let i,
      updateProcessedLen = 0;
    const variantBlockIntInc = this.variantBlockSize >>> 5,
      convertRet = this.converterFunc(srcString, this.remainder, this.remainderLen),
      chunkBinLen = convertRet["binLen"],
      chunk = convertRet["value"],
      chunkIntLen = chunkBinLen >>> 5;

    for (i = 0; i < chunkIntLen; i += variantBlockIntInc) {
      if (updateProcessedLen + this.variantBlockSize <= chunkBinLen) {
        this.intermediateState = this.roundFunc(chunk.slice(i, i + variantBlockIntInc), this.intermediateState);
        updateProcessedLen += this.variantBlockSize;
      }
    }
    this.processedLen += updateProcessedLen;
    this.remainder = chunk.slice(updateProcessedLen >>> 5);
    this.remainderLen = chunkBinLen % this.variantBlockSize;
    this.updateCalled = true;

    return this;
  }

  /**
   * Returns the desired SHA hash of the input fed in via `update` calls.
   *
   * @param format The desired output formatting
   * @param options Hashmap of output formatting options. `outputLen` must be specified for variable length hashes.
   *   `outputLen` replaces the now deprecated `shakeLen` key.
   * @returns The hash in the format specified.
   */
  getHash(format: "HEX", options?: { outputUpper?: boolean; outputLen?: number; shakeLen?: number }): string;
  getHash(format: "B64", options?: { b64Pad?: string; outputLen?: number; shakeLen?: number }): string;
  getHash(format: "BYTES", options?: { outputLen?: number; shakeLen?: number }): string;
  getHash(format: "UINT8ARRAY", options?: { outputLen?: number; shakeLen?: number }): Uint8Array;
  getHash(format: "ARRAYBUFFER", options?: { outputLen?: number; shakeLen?: number }): ArrayBuffer;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  getHash(format: any, options?: any): any {
    let i,
      finalizedState,
      outputBinLen = this.outputBinLen;

    const outputOptions = getOutputOpts(options);

    if (this.isVariableLen) {
      if (outputOptions["outputLen"] === -1) {
        throw new Error("Output length must be specified in options");
      }
      outputBinLen = outputOptions["outputLen"];
    }

    const formatFunc = getOutputConverter(format, outputBinLen, this.bigEndianMod, outputOptions);
    if (this.macKeySet && this.getMAC) {
      return formatFunc(this.getMAC(outputOptions));
    }

    finalizedState = this.finalizeFunc(
      this.remainder.slice(),
      this.remainderLen,
      this.processedLen,
      this.stateCloneFunc(this.intermediateState),
      outputBinLen,
    );
    for (i = 1; i < this.numRounds; i += 1) {
      /* Need to mask out bits that should be zero due to output not being a multiple of 32 */
      if (this.isVariableLen && outputBinLen % 32 !== 0) {
        finalizedState[finalizedState.length - 1] &= 0x00ffffff >>> (24 - (outputBinLen % 32));
      }
      finalizedState = this.finalizeFunc(
        finalizedState,
        outputBinLen,
        0,
        this.newStateFunc(this.shaVariant),
        outputBinLen,
      );
    }

    return formatFunc(finalizedState);
  }

  /**
   * Sets the HMAC key for an eventual `getHMAC` call.  Must be called immediately after jsSHA object instantiation.
   *
   * @param key The key used to calculate the HMAC
   * @param inputFormat The format of key.
   * @param options Hashmap of extra input options.
   */
  setHMACKey(key: string, inputFormat: "TEXT", options?: { encoding?: EncodingType }): void;
  setHMACKey(key: string, inputFormat: "B64" | "HEX" | "BYTES"): void;
  setHMACKey(key: ArrayBuffer, inputFormat: "ARRAYBUFFER"): void;
  setHMACKey(key: Uint8Array, inputFormat: "UINT8ARRAY"): void;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  setHMACKey(key: any, inputFormat: any, options?: any): void {
    if (!this.HMACSupported) {
      throw new Error("Variant does not support HMAC");
    }

    if (this.updateCalled) {
      throw new Error("Cannot set MAC key after calling update");
    }

    const keyOptions = options || {},
      keyConverterFunc = getStrConverter(inputFormat, keyOptions["encoding"] || "UTF8", this.bigEndianMod);

    this._setHMACKey(keyConverterFunc(key));
  }

  /**
   * Internal function that sets the MAC key.
   *
   * @param key The packed MAC key to use
   */
  protected _setHMACKey(key: packedValue): void {
    const blockByteSize = this.variantBlockSize >>> 3,
      lastArrayIndex = blockByteSize / 4 - 1;
    let i;
    if (this.numRounds !== 1) {
      throw new Error(mac_rounds_error);
    }

    if (this.macKeySet) {
      throw new Error("MAC key already set");
    }

    /* Figure out what to do with the key based on its size relative to
     * the hash's block size */
    if (blockByteSize < key["binLen"] / 8) {
      key["value"] = this.finalizeFunc(
        key["value"],
        key["binLen"],
        0,
        this.newStateFunc(this.shaVariant),
        this.outputBinLen,
      );
    }
    while (key["value"].length <= lastArrayIndex) {
      key["value"].push(0);
    }
    /* Create ipad and opad */
    for (i = 0; i <= lastArrayIndex; i += 1) {
      this.keyWithIPad[i] = key["value"][i] ^ 0x36363636;
      this.keyWithOPad[i] = key["value"][i] ^ 0x5c5c5c5c;
    }

    this.intermediateState = this.roundFunc(this.keyWithIPad, this.intermediateState);
    this.processedLen = this.variantBlockSize;

    this.macKeySet = true;
  }

  /**
   * Returns the the HMAC in the specified format using the key given by a previous `setHMACKey` call.
   *
   * @param format The desired output formatting.
   * @param options Hashmap of extra outputs options.
   * @returns The HMAC in the format specified.
   */
  getHMAC(format: "HEX", options?: { outputUpper?: boolean }): string;
  getHMAC(format: "B64", options?: { b64Pad?: string }): string;
  getHMAC(format: "BYTES"): string;
  getHMAC(format: "UINT8ARRAY"): Uint8Array;
  getHMAC(format: "ARRAYBUFFER"): ArrayBuffer;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  getHMAC(format: any, options?: any): any {
    const outputOptions = getOutputOpts(options),
      formatFunc = getOutputConverter(format, this.outputBinLen, this.bigEndianMod, outputOptions);

    return formatFunc(this._getHMAC());
  }

  /**
   * Internal function that returns the "raw" HMAC
   */
  protected _getHMAC(): number[] {
    let finalizedState;

    if (!this.macKeySet) {
      throw new Error("Cannot call getHMAC without first setting MAC key");
    }

    const firstHash = this.finalizeFunc(
      this.remainder.slice(),
      this.remainderLen,
      this.processedLen,
      this.stateCloneFunc(this.intermediateState),
      this.outputBinLen,
    );
    finalizedState = this.roundFunc(this.keyWithOPad, this.newStateFunc(this.shaVariant));
    finalizedState = this.finalizeFunc(
      firstHash,
      this.outputBinLen,
      this.variantBlockSize,
      finalizedState,
      this.outputBinLen,
    );

    return finalizedState;
  }
}

[evidence record sha256:2f00da06577809a6f6a15702a5e2e7f150911ea2033a69d615d6c959b67fa08b kind tool-call:read]
step 7: calling local:qwen3.6:35b-a3b
model error (retrying): the model spent all 8192 output tokens without emitting text or a tool call
model error (retrying): the model spent all 8192 output tokens without emitting text or a tool call
stopped: output-cap after 7 steps, 146596 tokens
gate typecheck not-applicable: package.json declares no typecheck script [evidence record sha256:dcc2e6bb8b1f041c21c5dfd3819d32ebdcbd7bd319999a9fb62dca01576f5227]
gate lint not-applicable: package.json declares no lint script [evidence record sha256:1612e8acd9c28a083bcf50616c59e7278c79cb75a579f1c7fd0a1fad57fd20ba]
gate format not-applicable: package.json declares no check-only format script, and running a writing formatter as a gate would edit the tree it is judging [evidence record sha256:de56ee4f49cd37e04578550df7589a9e701cb442e8b202888044c9c9e077f72e]
gate tests failed: the command exited 30 [evidence record sha256:e03bf573f1724df58f451c004cd2934415bb9dda77b6411261435f5b8b793a5f]
gate file-set passed: nothing changed and no file set was declared, so there is nothing to check [evidence record sha256:283dfbb336f3dfb095eccbc9d64286d14f6856de062a0fbfd1097ddbb64772c1]
gate placeholder passed: no placeholder marker was introduced by this change [evidence record sha256:6ab4a60c77fbee0a6e8337589bb53775332ffb0cc3347a678c83b90c1c1257c8]
gate secret-scan passed: no known credential pattern appears in the added lines [evidence record sha256:0d1bddaaa0335ae01c8ded7b8c8df77a28e4d5968164123819f75b8963b3a344]
gate behaviour-probe passed: 0 changed function(s) still answer to their inputs. [evidence record sha256:e4544917a11298f6a754745d2738fe0f6523c4e67aad3f27582878f46f5e3ebd]
gate diff-budget passed (advisory): within budget: 0 file(s) and 0 added line(s) [evidence record sha256:e6cc394dbdfbda0b24d095bc7c9b221e9009e6f31b3a42113876248f0ffd3af6]
ratchet accepted attempt 1: the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage) [evidence record sha256:c649ab45076c35eba5367c6160b135142e15eef4e01255c050ad2c38ca7c843b]
auto-resolve attempt 2 of 2
step 1: calling local:qwen3.6:35b-a3b
plan: Looking at the test failures, I see a pattern related to packed little-endian concatenation (`packedLEConcat`) and encoding functions (`encode_string`, `byte_pad`). Let me examine the source code.
tool list <- {"path":"."}
tool list ok: .git/
.github/
.gitignore
.husky/
.npmignore
.nyc_output/
.prettierignore
CHANGELOG.md
CONTRIBUTING.md
LICENSE
README.md
SECURITY.md
bower.json
coverage/
dist/
eslint.config.js
karma.conf.js
node_modules/
package-lock.json
package.json
rollup.config.mjs
scripts/
src/
test/
tsconfig.json
[evidence record sha256:b6531070df0e5ef77316c14a789af45c5259ed643be75ed73b256a48f5097bf4 kind tool-call:list]
step 2: calling local:qwen3.6:35b-a3b
tool list <- {"path":"src"}
tool list ok: common.ts
converters.ts
custom_types.ts
license_header.es3.txt
license_header.es6.txt
primitives_32.ts
primitives_64.ts
sha.ts
sha1.ts
sha256.ts
sha3.ts
sha512.ts
[evidence record sha256:e906a5d0628f40af7acdf1167de92e34328795e60eaafa5b86afa63514edc162 kind tool-call:list]
tool list <- {"path":"test/src"}
tool list ok: common.ts
test_common.ts
test_converters.ts
test_primitives_32.ts
test_primitives_64.ts
test_sha.ts
test_sha1.ts
test_sha256.ts
test_sha3.ts
test_sha3_consts.ts
test_sha512.ts
[evidence record sha256:1cd687d3799f2be98a6e28a0212e00b15d963961345fe807d7e1d14151603041 kind tool-call:list]
step 3: calling local:qwen3.6:35b-a3b
tool read <- {"path":"test/src/test_common.ts"}
tool read ok: import { describe, it } from "mocha";
import sinon from "sinon";
import { assert } from "chai";
import { getOutputOpts, parseInputOption, packedLEConcat, jsSHABase } from "../../src/common";
import {
  FixedLengthOptionsEncodingType,
  FixedLengthOptionsNoEncodingType,
  FormatNoTextType,
  packedValue,
} from "../../src/custom_types";

describe("Test packedLEConcat", () => {
  it("For 2 0-byte Values", () => {
    assert.deepEqual(packedLEConcat({ value: [], binLen: 0 }, { value: [], binLen: 0 }), { value: [], binLen: 0 });
  });

  it("For 2 3-byte Values", () => {
    assert.deepEqual(packedLEConcat({ value: [0x00112233], binLen: 24 }, { value: [0x00aabbcc], binLen: 24 }), {
      value: [0xcc112233 | 0, 0x0000aabb],
      binLen: 48,
    });
  });

  it("For 2 4-byte Values", () => {
    assert.deepEqual(packedLEConcat({ value: [0x11223344], binLen: 32 }, { value: [0xaabbccdd], binLen: 32 }), {
      value: [0x11223344, 0xaabbccdd],
      binLen: 64,
    });
  });

  it("For 1 1-byte and 1 3-byte Value", () => {
    assert.deepEqual(packedLEConcat({ value: [0x00000011], binLen: 8 }, { value: [0x00aabbcc], binLen: 24 }), {
      value: [0xaabbcc11 | 0],
      binLen: 32,
    });
  });
});

describe("Test parseInputOption", () => {
  it("For Fully Specified Value", () => {
    assert.deepEqual(parseInputOption("kmacKey", { value: "00112233", format: "HEX" }, 1), {
      value: [0x33221100],
      binLen: 32,
    });
  });

  it("For Empty but Optional Value", () => {
    assert.deepEqual(parseInputOption("kmacKey", undefined, 1, { value: [], binLen: 0 }), { value: [], binLen: 0 });
  });

  it("For Empty but Required Value", () => {
    assert.throws(() => {
      parseInputOption("kmacKey", undefined, 1);
    }, "kmacKey must include a value and format");
  });

  it("For Value Missing value Key", () => {
    assert.throws(() => {
      // @ts-ignore - Deliberately bad value for test
      parseInputOption("kmacKey", { format: "HEX" }, 1);
    }, "kmacKey must include a value and format");
  });

  it("For Value Missing binLen Key", () => {
    assert.throws(() => {
      // @ts-ignore - Deliberately bad value for test
      parseInputOption("kmacKey", { value: "TEST" }, 1);
    }, "kmacKey must include a value and format");
  });
});

describe("Test getOutputOpts", () => {
  it("Empty Input", () => {
    assert.deepEqual(getOutputOpts(), { outputUpper: false, b64Pad: "=", outputLen: -1 });
  });

  it("b64Pad Specified", () => {
    assert.deepEqual(getOutputOpts({ b64Pad: "#" }), { outputUpper: false, b64Pad: "#", outputLen: -1 });
  });

  it("outputLen Specified", () => {
    assert.deepEqual(getOutputOpts({ outputLen: 16, shakeLen: 8 }), { outputUpper: false, b64Pad: "=", outputLen: 16 });
  });

  it("shakeLen Specified", () => {
    assert.deepEqual(getOutputOpts({ shakeLen: 8 }), { outputUpper: false, b64Pad: "=", outputLen: 8 });
  });

  it("Invalid shakeLen", () => {
    assert.throws(() => {
      getOutputOpts({ shakeLen: 1 });
    }, "Output length must be a multiple of 8");
  });

  it("Invalid outputLen", () => {
    assert.throws(() => {
      getOutputOpts({ outputLen: 1 });
    }, "Output length must be a multiple of 8");
  });

  it("Invalid b64Pad", () => {
    assert.throws(() => {
      // @ts-ignore - Deliberate bad b64Pad value to test exceptions
      getOutputOpts({ b64Pad: 1 });
    }, "Invalid b64Pad formatting option");
  });

  it("Invalid outputUpper", () => {
    assert.throws(() => {
      // @ts-ignore - Deliberate bad outputUpper value to test exceptions
      getOutputOpts({ outputUpper: 1 });
    }, "Invalid outputUpper formatting option");
  });
});

describe("Test jsSHABase", () => {
  const stubbedStrConverter = sinon.stub(),
    stubbedRound = sinon.stub(),
    stubbedNewState = sinon.stub(),
    stubbedFinalize = sinon.stub(),
    stubbedStateClone = sinon.stub(),
    dummyVals = [
      0x11223344, 0xaabbccdd, 0xdeadbeef, 0xfacefeed, 0xbaddcafe, 0xdeadcafe, 0xdead2bad, 0xdeaddead, 0xcafed00d,
      0xdecafbad, 0xfee1dead, 0xdeadfa11,
    ];

  class jsSHAATest extends jsSHABase<number[], "SHA-TEST"> {
    intermediateState: number[];
    variantBlockSize: number;
    bigEndianMod: -1 | 1;
    outputBinLen: number;
    isVariableLen: boolean;
    HMACSupported: boolean;

    /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
    converterFunc: (input: any, existingBin: number[], existingBinLen: number) => packedValue;
    roundFunc: (block: number[], H: number[]) => number[];
    finalizeFunc: (remainder: number[], remainderBinLen: number, processedBinLen: number, H: number[]) => number[];
    stateCloneFunc: (state: number[]) => number[];
    newStateFunc: (variant: "SHA-TEST") => number[];
    getMAC: () => number[];

    constructor(variant: "SHA-TEST", inputFormat: "TEXT", options?: FixedLengthOptionsEncodingType);
    constructor(variant: "SHA-TEST", inputFormat: FormatNoTextType, options?: FixedLengthOptionsNoEncodingType);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    constructor(variant: any, inputFormat: any, options?: any) {
      super(variant, inputFormat, options);

      this.bigEndianMod = -1;
      this.converterFunc = stubbedStrConverter;
      this.roundFunc = stubbedRound;
      this.stateCloneFunc = stubbedStateClone;
      this.newStateFunc = stubbedNewState;
      this.finalizeFunc = stubbedFinalize;
      // eslint-disable-next-line @typescript-eslint/unbound-method
      this.getMAC = this._getHMAC;

      this.intermediateState = [0, 0];
      this.variantBlockSize = 64;
      this.outputBinLen = 64;
      this.isVariableLen = false;
      this.HMACSupported = true;
    }

    /*
     * Dirty hack function to expose the protected members of jsSHABase
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    getter(propName: string): any {
      // @ts-ignore - Override "any" ban as this is only used in testing
      return this[propName];
    }

    /*
     * Dirty hack function to expose the protected members of jsSHABase
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    setter(propName: string, value: any): void {
      // @ts-ignore - Override "any" ban as this is only used in testing
      this[propName] = value;
    }
  }

  it("Test Constructor with Empty Options", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");

    assert.equal(stubbedJsSHA.getter("inputFormat"), "HEX");
    assert.equal(stubbedJsSHA.getter("utfType"), "UTF8");
    assert.equal(stubbedJsSHA.getter("shaVariant"), "SHA-TEST");
    assert.equal(stubbedJsSHA.getter("numRounds"), 1);
    assert.equal(stubbedJsSHA.getter("remainderLen"), 0);
    assert.equal(stubbedJsSHA.getter("processedLen"), 0);
    assert.isFalse(stubbedJsSHA.getter("updateCalled"));
    assert.isFalse(stubbedJsSHA.getter("macKeySet"));
    assert.deepEqual(stubbedJsSHA.getter("remainder"), []);
    assert.deepEqual(stubbedJsSHA.getter("keyWithIPad"), []);
    assert.deepEqual(stubbedJsSHA.getter("keyWithOPad"), []);
  });

  it("Test Constructor with Bad numRounds", () => {
    assert.throws(() => {
      new jsSHAATest("SHA-TEST", "HEX", { numRounds: 1.2 });
    }, "numRounds must a integer >= 1");

    assert.throws(() => {
      new jsSHAATest("SHA-TEST", "HEX", { numRounds: -1 });
    }, "numRounds must a integer >= 1");
  });

  it("Test update", () => {
    /*
     * This is rather difficult to test so we want to check a few basic things:
     *   1. It returns a reference to the jsSHA object
     *   2. It passed the input to the string conversion function correctly
     *   3. It did *not* call the round function when the input was smaller than the block size
     *   4. Intermediate state was untouched but remainder variables are updated
     *   5. It *did* call the round function when the input was greater than or equal to than the block size
     *   6. Intermediate state and associated variables are set correctly
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX"),
      inputStr = "ABCD";
    sinon.reset();

    stubbedStrConverter
      .onFirstCall()
      .returns({ value: [dummyVals[0]], binLen: 32 })
      .onSecondCall()
      .returns({ value: [dummyVals[0], dummyVals[0]], binLen: 64 });
    stubbedRound.returns([dummyVals[1], dummyVals[2]]);

    // Check #1
    assert.equal(stubbedJsSHA, stubbedJsSHA.update(inputStr));
    // Check #2
    assert.isTrue(stubbedStrConverter.calledOnceWith(inputStr, [], 0));
    // Check #3
    assert.isFalse(stubbedRound.called);
    // Check #4
    assert.deepEqual(stubbedJsSHA.getter("intermediateState"), [0, 0]);
    assert.deepEqual(stubbedJsSHA.getter("remainder"), [dummyVals[0]]);
    assert.equal(stubbedJsSHA.getter("remainderLen"), 32);
    assert.equal(stubbedJsSHA.getter("processedLen"), 0);
    assert.isTrue(stubbedJsSHA.getter("updateCalled"));

    stubbedJsSHA.update(inputStr);
    // Check #2 again to make sure state is being passed correctly
    assert.equal(stubbedStrConverter.callCount, 2);
    assert.isTrue(stubbedStrConverter.getCall(1).calledWithExactly(inputStr, [dummyVals[0]], 32));
    // Check #5
    assert.isTrue(stubbedRound.calledOnceWith([dummyVals[0], dummyVals[0]], [0, 0]));

    // Check #6
    assert.deepEqual(stubbedJsSHA.getter("intermediateState"), [dummyVals[1], dummyVals[2]]);
    assert.deepEqual(stubbedJsSHA.getter("remainder"), []);
    assert.equal(stubbedJsSHA.getter("remainderLen"), 0);
    assert.equal(stubbedJsSHA.getter("processedLen"), 64);
  });

  it("Test getHash Without Needed outputLen ", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");

    stubbedJsSHA.setter("isVariableLen", true);
    assert.throws(() => {
      stubbedJsSHA.getHash("HEX", {});
    }, "Output length must be specified in options");
  });

  it("Test getHash", () => {
    /*
     * Check a few basic things:
     *   1. The output of getHash should equal the first outputBinLen bits of the output of finalizeFunc
     *   2. intermediateState and remainder should not be changed by calling getHash
     *   3. finalize should be called once with the correct inputs
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    sinon.reset();
    stubbedFinalize.returns([dummyVals[0], dummyVals[1]]);
    stubbedStateClone.returns([dummyVals[2], dummyVals[3]]);

    stubbedJsSHA.setter("intermediateState", [dummyVals[4]]);
    const intermediateState = stubbedJsSHA.getter("intermediateState");
    stubbedJsSHA.setter("remainder", [dummyVals[5]]);
    const remainder = stubbedJsSHA.getter("remainder");
    stubbedJsSHA.setter("remainderLen", 32);
    stubbedJsSHA.setter("processedLen", 64);

    // Check #1
    assert.equal(stubbedJsSHA.getHash("HEX"), dummyVals[0].toString(16) + dummyVals[1].toString(16));

    // Check #2, note deliberate use of equal vs deepEqual
    assert.equal(intermediateState, stubbedJsSHA.getter("intermediateState"));
    assert.equal(remainder, stubbedJsSHA.getter("remainder"));

    // Check #3
    assert.isTrue(
      stubbedFinalize.calledOnceWith(
        [dummyVals[5]],
        32,
        64,
        [dummyVals[2], dummyVals[3]],
        stubbedJsSHA.getter("outputBinLen"),
      ),
    );
  });

  it("Test getHash for SHAKE", () => {
    /*
     * Check a few basic things:
     *   1. The output of getHash should equal the first outputLen bits of the output of finalizeFunc
     *   2. finalize should be called once with the correct inputs
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    stubbedJsSHA.setter("isVariableLen", true);
    sinon.reset();
    stubbedFinalize.returns([dummyVals[0], dummyVals[1]]);
    stubbedStateClone.returns([dummyVals[2], dummyVals[3]]);

    stubbedJsSHA.setter("intermediateState", [dummyVals[4]]);
    stubbedJsSHA.setter("remainder", [dummyVals[5]]);
    stubbedJsSHA.setter("remainderLen", 32);
    stubbedJsSHA.setter("processedLen", 64);

    // Check #1
    assert.equal(stubbedJsSHA.getHash("HEX", { outputLen: 32 }), dummyVals[0].toString(16));

    // Check #2
    assert.isTrue(stubbedFinalize.calledOnceWith([dummyVals[5]], 32, 64, [dummyVals[2], dummyVals[3]], 32));
  });

  it("Test getHash for numRounds=3", () => {
    /*
     * Check a few basic things:
     *   1. The output of getHash should equal the output of last finalizeFunc call
     *   2. finalizeFunc should be called numRound times
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX", { numRounds: 3 });
    sinon.reset();
    stubbedFinalize.returns([dummyVals[0], dummyVals[1]]).onCall(2).returns([dummyVals[2], dummyVals[3]]);

    // Check #1
    assert.equal(stubbedJsSHA.getHash("HEX"), dummyVals[2].toString(16) + dummyVals[3].toString(16));

    // Check #2
    assert.equal(stubbedFinalize.callCount, 3);
  });

  it("Test getHash for SHAKE numRounds=3", () => {
    /*
     * Check a few basic things:
     *   1. The output of getHash should equal the output of last finalizeFunc call
     *   2. finalizeFunc should be called numRound times
     *   3. The last numRound-1 calls of finalizeFunc should have the last 32-outputLen bits 0ed out
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX", { numRounds: 3 });
    stubbedJsSHA.setter("isVariableLen", true);
    sinon.reset();
    stubbedFinalize.returns([dummyVals[0], dummyVals[1]]).onCall(2).returns([dummyVals[2], dummyVals[3]]);

    // Check #1
    assert.equal(stubbedJsSHA.getHash("HEX", { outputLen: 24 }), dummyVals[2].toString(16).substr(0, 6));

    // Check #2
    assert.equal(stubbedFinalize.callCount, 3);

    // Check #3
    stubbedFinalize.getCall(1).calledWith([dummyVals[0], dummyVals[1] & 0x00ffffff], 24);
    stubbedFinalize.getCall(2).calledWith([dummyVals[0], dummyVals[1] & 0x00ffffff], 24);
  });

  it("Test setHMACKey with Short Key", () => {
    /*
     * Check a few basic things:
     *   1. keyWithIPad is set correctly
     *   2. keyWithOPad is set correctly
     *   3. The round function was called and its return value stored as intermediateState
     *   4. macKeySet was set
     *   5. processedLen was updated
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    sinon.reset();
    stubbedRound.returns([dummyVals[0], dummyVals[1]]);
    stubbedJsSHA.setHMACKey("ABCD", "TEXT");

    // Check #1
    assert.deepEqual(
      stubbedJsSHA.getter("keyWithIPad"),
      [0x41424344, 0].map((val) => {
        return val ^ 0x36363636;
      }),
    );

    // Check #2
    assert.deepEqual(
      stubbedJsSHA.getter("keyWithOPad"),
      [0x41424344, 0].map((val) => {
        return val ^ 0x5c5c5c5c;
      }),
    );

    // Check #3
    assert.isTrue(
      stubbedRound.calledOnceWithExactly(
        [0x41424344, 0].map((val) => {
          return val ^ 0x36363636;
        }),
        [0, 0],
      ),
    );
    assert.deepEqual(stubbedJsSHA.getter("intermediateState"), [dummyVals[0], dummyVals[1]]);

    // Check #4
    assert.isTrue(stubbedJsSHA.getter("macKeySet"));

    // Check #5
    assert.equal(stubbedJsSHA.getter("processedLen"), stubbedJsSHA.getter("variantBlockSize"));
  });

  it("Test setHMACKey with Long Key", () => {
    /*
     * Check a few basic things:
     *   1. Finalize was called with the correct keying material
     *   2. keyWithIPad is set correctly
     *   3. keyWithOPad is set correctly
     *   4. The round function was called with the input set as the output from finalize and its return value stored as intermediateState
     */
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX"),
      inputStr = "ABCDEFGHABCD",
      inputStrPacked = [0x41424344, 0x45464748, 0x41424344];
    sinon.reset();
    stubbedFinalize.returns([dummyVals[0], dummyVals[1]]);
    stubbedRound.returns([dummyVals[2], dummyVals[3]]);
    stubbedNewState.returns([dummyVals[4], dummyVals[5]]);

    // Need to call setHMACKey with more than 64-bits of key material to test handling of "large" key sizes
    stubbedJsSHA.setHMACKey(inputStr, "TEXT");

    // Check #1
    assert.isTrue(
      stubbedFinalize.calledOnceWithExactly(
        inputStrPacked,
        96,
        0,
        [dummyVals[4], dummyVals[5]],
        stubbedJsSHA.getter("outputBinLen"),
      ),
    );

    // Check #2
    assert.deepEqual(
      stubbedJsSHA.getter("keyWithIPad"),
      [dummyVals[0], dummyVals[1]].map((val) => {
        return val ^ 0x36363636;
      }),
    );

    // Check #3
    assert.deepEqual(
      stubbedJsSHA.getter("keyWithOPad"),
      [dummyVals[0], dummyVals[1]].map((val) => {
        return val ^ 0x5c5c5c5c;
      }),
    );

    // Check #4
    assert.isTrue(
      stubbedRound.calledOnceWithExactly(
        [dummyVals[0], dummyVals[1]].map((val) => {
          return val ^ 0x36363636;
        }),
        [0, 0],
      ),
    );
    assert.deepEqual(stubbedJsSHA.getter("intermediateState"), [dummyVals[2], dummyVals[3]]);
  });

  it("Test setHMACKey Error on Double Call", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    stubbedJsSHA.setter("macKeySet", true);
    sinon.reset();

    assert.throws(() => {
      stubbedJsSHA.setHMACKey("ABCD", "TEXT");
    }, "MAC key already set");
  });

  it("Test setHMACKey Error on numRounds > 1", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX", { numRounds: 2 });
    sinon.reset();

    assert.throws(() => {
      stubbedJsSHA.setHMACKey("ABCD", "TEXT");
    }, "Cannot set numRounds with MAC");
  });

  it("Test setHMACKey Error on After update", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    stubbedJsSHA.setter("updateCalled", true);
    sinon.reset();

    assert.throws(() => {
      stubbedJsSHA.setHMACKey("ABCD", "TEXT");
    }, "Cannot set MAC key after calling update");
  });

  it("Test setHMACKey Error on Unsupported Variant", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    stubbedJsSHA.setter("HMACSupported", false);
    sinon.reset();

    assert.throws(() => {
      stubbedJsSHA.setHMACKey("ABCD", "TEXT");
    }, "Variant does not support HMAC");
  });

  it("Test HMAC Return", () => {
    /*
     * Check a few basic things:
     *   1. It returns the formatted output of the last finalizeFunc call
     *   2. finalizeFunc was called with a clone of the remainder and correct parameters
     *   3. roundFunc was called with keyWithOPad
     *   4. finalizeFunc was called with the output of the previous finalizeFunc's output and the roundFunc's state
     *   5. remainder, intermediateState, and remainderLen remain untouched
     *   6. A call to getHash actually returns the HMAC
     */
    sinon.reset();
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX"),
      intermediateState = [dummyVals[6], dummyVals[7]],
      remainder = [dummyVals[0]],
      keyWithOPad = [dummyVals[10], dummyVals[11]],
      newState = [dummyVals[8], dummyVals[9]],
      clonedState = [dummyVals[6], dummyVals[7]],
      getMACStub = sinon.stub().returns([[dummyVals[2]], dummyVals[3]]);

    stubbedFinalize
      .onCall(0)
      .returns([dummyVals[0], dummyVals[1]])
      .onCall(1)
      .returns([[dummyVals[2]], dummyVals[3]]);
    stubbedRound.returns([dummyVals[4], dummyVals[5]]);
    stubbedStateClone.returns(clonedState);
    stubbedNewState.returns(newState);

    stubbedJsSHA.setter("macKeySet", true);
    stubbedJsSHA.setter("processedLen", 64);
    stubbedJsSHA.setter("keyWithOPad", keyWithOPad);
    stubbedJsSHA.setter("remainder", remainder);
    stubbedJsSHA.setter("remainderLen", 32);
    stubbedJsSHA.setter("intermediateState", intermediateState);

    // Check #1
    assert.equal(stubbedJsSHA.getHMAC("HEX"), "deadbeeffacefeed");

    // Check #2
    stubbedFinalize
      .getCall(0)
      .calledWithExactly(
        remainder,
        32,
        stubbedJsSHA.getter("outputBinLen"),
        clonedState,
        stubbedJsSHA.getter("outputBinLen"),
      );

    // Check #3
    stubbedRound.calledOnceWithExactly(keyWithOPad, newState);

    // Check #4
    stubbedFinalize
      .getCall(1)
      .calledWithExactly(
        [dummyVals[0], dummyVals[1]],
        stubbedJsSHA.getter("outputBinLen"),
        stubbedJsSHA.getter("variantBlockSize"),
        [dummyVals[4], dummyVals[5]],
        stubbedJsSHA.getter("outputBinLen"),
      );

    // Check #5
    assert.equal(stubbedJsSHA.getter("remainder"), remainder);
    assert.equal(stubbedJsSHA.getter("remainderLen"), 32);
    assert.equal(stubbedJsSHA.getter("intermediateState"), intermediateState);

    // Check #6
    stubbedJsSHA.setter("getMAC", getMACStub);
    stubbedJsSHA.getHash("HEX");
    assert.equal(getMACStub.callCount, 1);
  });

  it("Test getHMAC Error on Not Setting MAC Key", () => {
    const stubbedJsSHA = new jsSHAATest("SHA-TEST", "HEX");
    sinon.reset();

    assert.throws(() => {
      stubbedJsSHA.getHMAC("HEX");
    }, "Cannot call getHMAC without first setting MAC key");
  });
});

[evidence record sha256:c74e48dab79c9f900d8c13f97ef5b90841670831d2ad940cfb6673f5cccd6a15 kind tool-call:read]
step 4: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/common.ts"}
tool read ok: import { getStrConverter, getOutputConverter } from "./converters";

import {
  FormatType,
  EncodingType,
  FixedLengthOptionsEncodingType,
  FixedLengthOptionsNoEncodingType,
  FormatNoTextType,
  packedValue,
  GenericInputType,
} from "./custom_types";

export const TWO_PWR_32 = 4294967296;

/* Constant used in SHA-2 families */
export const K_sha2 = [
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98,
  0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
  0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8,
  0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
  0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
  0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
  0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
  0xc67178f2,
];

/* Constant used in SHA-2 families */
export const H_trunc = [0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4];

/* Constant used in SHA-2 families */
export const H_full = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];

export const sha_variant_error = "Chosen SHA variant is not supported";
export const mac_rounds_error = "Cannot set numRounds with MAC";

/**
 * Concatenates 2 packed arrays. Clobbers array `a`.
 *
 * @param a First array to concatenate.
 * @param b Second array to concatenate.
 * @returns The concatentation of `a` + `b`.
 */
export function packedLEConcat(a: packedValue, b: packedValue): packedValue {
  let i, arrOffset;
  const aByteLen = a["binLen"] >>> 3,
    bByteLen = b["binLen"] >>> 3,
    leftShiftAmount = aByteLen << 3,
    rightShiftAmount = (4 - aByteLen) << 3;

  /* If a only contains "full" integers, we can just use concat which is so much easier */
  if (aByteLen % 4 === 0) {
    for (i = 0; i < bByteLen; i += 4) {
      arrOffset = (aByteLen + i) >>> 2;
      /* Left shift chops off bits over 32-bits */
      a["value"][arrOffset] |= b["value"][i >>> 2] << leftShiftAmount;
      a["value"].push(0);
      a["value"][arrOffset + 1] |= b["value"][i >>> 2] >>> rightShiftAmount;
    }

    /* Since an unconditional push was performed above, we may have pushed an extra value if it could have been
       encoded without it.  Check if popping an int off (reducing total length by 4 bytes) is still bigger than the
       needed size. */
    if ((a["value"].length << 2) - 4 >= bByteLen + aByteLen) {
      a["value"].pop();
    }

    return { value: a["value"], binLen: a["binLen"] + b["binLen"] };
  } else {
    return { value: a["value"].concat(b["value"]), binLen: a["binLen"] + b["binLen"] };
  }
}

/**
 * Validate hash list containing output formatting options, ensuring presence of every option or adding the default
 * value.
 *
 * @param options Hashmap of output formatting options from user.
 * @returns Validated hashmap containing output formatting options.
 */
export function getOutputOpts(options?: {
  outputUpper?: boolean;
  b64Pad?: string;
  shakeLen?: number;
  outputLen?: number;
}): { outputUpper: boolean; b64Pad: string; outputLen: number } {
  const retVal = { outputUpper: false, b64Pad: "=", outputLen: -1 },
    outputOptions: { outputUpper?: boolean; b64Pad?: string; shakeLen?: number; outputLen?: number } = options || {},
    lenErrstr = "Output length must be a multiple of 8";

  retVal["outputUpper"] = outputOptions["outputUpper"] || false;

  if (outputOptions["b64Pad"]) {
    retVal["b64Pad"] = outputOptions["b64Pad"];
  }

  if (outputOptions["outputLen"]) {
    if (outputOptions["outputLen"] % 8 !== 0) {
      throw new Error(lenErrstr);
    }
    retVal["outputLen"] = outputOptions["outputLen"];
  } else if (outputOptions["shakeLen"]) {
    if (outputOptions["shakeLen"] % 8 !== 0) {
      throw new Error(lenErrstr);
    }
    retVal["outputLen"] = outputOptions["shakeLen"];
  }

  if ("boolean" !== typeof retVal["outputUpper"]) {
    throw new Error("Invalid outputUpper formatting option");
  }

  if ("string" !== typeof retVal["b64Pad"]) {
    throw new Error("Invalid b64Pad formatting option");
  }

  return retVal;
}

/**
 * Parses an external constructor object and returns a packed number, if possible.
 *
 * @param key The human-friendly key name to prefix any errors with
 * @param value The input value object to parse
 * @param bigEndianMod Modifier for whether hash function is big or small endian.
 * @param fallback Fallback value if `value` is undefined.  If not present and `value` is undefined, an Error is thrown
 */
export function parseInputOption(
  key: string,
  value: GenericInputType | undefined,
  bigEndianMod: -1 | 1,
  fallback?: packedValue,
): packedValue {
  const errStr = key + " must include a value and format";
  if (!value) {
    if (!fallback) {
      throw new Error(errStr);
    }
    return fallback;
  }

  if (typeof value["value"] === "undefined" || !value["format"]) {
    throw new Error(errStr);
  }

  return getStrConverter(
    value["format"],
    // @ts-ignore - the value of encoding gets value checked by getStrConverter
    value["encoding"] || "UTF8",
    bigEndianMod,
  )(value["value"]);
}

export abstract class jsSHABase<StateT, VariantT> {
  /**
   * @param variant The desired SHA variant.
   * @param inputFormat The input format to be used in future `update` calls.
   * @param options Hashmap of extra input options.
   */
  /* Needed inputs */
  protected readonly shaVariant: VariantT;
  protected readonly inputFormat: FormatType;
  protected readonly utfType: EncodingType;
  protected readonly numRounds: number;

  /* State */
  protected abstract intermediateState: StateT;
  protected keyWithIPad: number[];
  protected keyWithOPad: number[];
  protected remainder: number[];
  protected remainderLen: number;
  protected updateCalled: boolean;
  protected processedLen: number;
  protected macKeySet: boolean;

  /* Variant specifics */
  protected abstract readonly variantBlockSize: number;
  protected abstract readonly bigEndianMod: -1 | 1;
  protected abstract readonly outputBinLen: number;
  protected abstract readonly isVariableLen: boolean;
  protected abstract readonly HMACSupported: boolean;

  /* Functions */
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  protected abstract readonly converterFunc: (input: any, existingBin: number[], existingBinLen: number) => packedValue;
  protected abstract readonly roundFunc: (block: number[], H: StateT) => StateT;
  protected abstract readonly finalizeFunc: (
    remainder: number[],
    remainderBinLen: number,
    processedBinLen: number,
    H: StateT,
    outputLen: number,
  ) => number[];
  protected abstract readonly stateCloneFunc: (state: StateT) => StateT;
  protected abstract readonly newStateFunc: (variant: VariantT) => StateT;
  protected abstract readonly getMAC: ((options: { outputLen: number }) => number[]) | null;

  protected constructor(variant: VariantT, inputFormat: "TEXT", options?: FixedLengthOptionsEncodingType);
  protected constructor(variant: VariantT, inputFormat: FormatNoTextType, options?: FixedLengthOptionsNoEncodingType);
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  protected constructor(variant: any, inputFormat: any, options?: any) {
    const inputOptions = options || {};
    this.inputFormat = inputFormat;

    this.utfType = inputOptions["encoding"] || "UTF8";
    this.numRounds = inputOptions["numRounds"] || 1;

    // @ts-ignore - The spec actually says ToString is called on the first parseInt argument so it's OK to use it here
    // to check if an arugment is an integer. This cheat would break if it's used to get the value of the argument.
    if (isNaN(this.numRounds) || this.numRounds !== parseInt(this.numRounds, 10) || 1 > this.numRounds) {
      throw new Error("numRounds must a integer >= 1");
    }

    this.shaVariant = variant;
    this.remainder = [];
    this.remainderLen = 0;
    this.updateCalled = false;
    this.processedLen = 0;
    this.macKeySet = false;
    this.keyWithIPad = [];
    this.keyWithOPad = [];
  }

  /**
   * Hashes as many blocks as possible.  Stores the rest for either a future update or getHash call.
   *
   * @param srcString The input to be hashed.
   * @returns A reference to the object.
   */
  update(srcString: string | ArrayBuffer | Uint8Array): this {
    let i,
      updateProcessedLen = 0;
    const variantBlockIntInc = this.variantBlockSize >>> 5,
      convertRet = this.converterFunc(srcString, this.remainder, this.remainderLen),
      chunkBinLen = convertRet["binLen"],
      chunk = convertRet["value"],
      chunkIntLen = chunkBinLen >>> 5;

    for (i = 0; i < chunkIntLen; i += variantBlockIntInc) {
      if (updateProcessedLen + this.variantBlockSize <= chunkBinLen) {
        this.intermediateState = this.roundFunc(chunk.slice(i, i + variantBlockIntInc), this.intermediateState);
        updateProcessedLen += this.variantBlockSize;
      }
    }
    this.processedLen += updateProcessedLen;
    this.remainder = chunk.slice(updateProcessedLen >>> 5);
    this.remainderLen = chunkBinLen % this.variantBlockSize;
    this.updateCalled = true;

    return this;
  }

  /**
   * Returns the desired SHA hash of the input fed in via `update` calls.
   *
   * @param format The desired output formatting
   * @param options Hashmap of output formatting options. `outputLen` must be specified for variable length hashes.
   *   `outputLen` replaces the now deprecated `shakeLen` key.
   * @returns The hash in the format specified.
   */
  getHash(format: "HEX", options?: { outputUpper?: boolean; outputLen?: number; shakeLen?: number }): string;
  getHash(format: "B64", options?: { b64Pad?: string; outputLen?: number; shakeLen?: number }): string;
  getHash(format: "BYTES", options?: { outputLen?: number; shakeLen?: number }): string;
  getHash(format: "UINT8ARRAY", options?: { outputLen?: number; shakeLen?: number }): Uint8Array;
  getHash(format: "ARRAYBUFFER", options?: { outputLen?: number; shakeLen?: number }): ArrayBuffer;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  getHash(format: any, options?: any): any {
    let i,
      finalizedState,
      outputBinLen = this.outputBinLen;

    const outputOptions = getOutputOpts(options);

    if (this.isVariableLen) {
      if (outputOptions["outputLen"] === -1) {
        throw new Error("Output length must be specified in options");
      }
      outputBinLen = outputOptions["outputLen"];
    }

    const formatFunc = getOutputConverter(format, outputBinLen, this.bigEndianMod, outputOptions);
    if (this.macKeySet && this.getMAC) {
      return formatFunc(this.getMAC(outputOptions));
    }

    finalizedState = this.finalizeFunc(
      this.remainder.slice(),
      this.remainderLen,
      this.processedLen,
      this.stateCloneFunc(this.intermediateState),
      outputBinLen,
    );
    for (i = 1; i < this.numRounds; i += 1) {
      /* Need to mask out bits that should be zero due to output not being a multiple of 32 */
      if (this.isVariableLen && outputBinLen % 32 !== 0) {
        finalizedState[finalizedState.length - 1] &= 0x00ffffff >>> (24 - (outputBinLen % 32));
      }
      finalizedState = this.finalizeFunc(
        finalizedState,
        outputBinLen,
        0,
        this.newStateFunc(this.shaVariant),
        outputBinLen,
      );
    }

    return formatFunc(finalizedState);
  }

  /**
   * Sets the HMAC key for an eventual `getHMAC` call.  Must be called immediately after jsSHA object instantiation.
   *
   * @param key The key used to calculate the HMAC
   * @param inputFormat The format of key.
   * @param options Hashmap of extra input options.
   */
  setHMACKey(key: string, inputFormat: "TEXT", options?: { encoding?: EncodingType }): void;
  setHMACKey(key: string, inputFormat: "B64" | "HEX" | "BYTES"): void;
  setHMACKey(key: ArrayBuffer, inputFormat: "ARRAYBUFFER"): void;
  setHMACKey(key: Uint8Array, inputFormat: "UINT8ARRAY"): void;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  setHMACKey(key: any, inputFormat: any, options?: any): void {
    if (!this.HMACSupported) {
      throw new Error("Variant does not support HMAC");
    }

    if (this.updateCalled) {
      throw new Error("Cannot set MAC key after calling update");
    }

    const keyOptions = options || {},
      keyConverterFunc = getStrConverter(inputFormat, keyOptions["encoding"] || "UTF8", this.bigEndianMod);

    this._setHMACKey(keyConverterFunc(key));
  }

  /**
   * Internal function that sets the MAC key.
   *
   * @param key The packed MAC key to use
   */
  protected _setHMACKey(key: packedValue): void {
    const blockByteSize = this.variantBlockSize >>> 3,
      lastArrayIndex = blockByteSize / 4 - 1;
    let i;
    if (this.numRounds !== 1) {
      throw new Error(mac_rounds_error);
    }

    if (this.macKeySet) {
      throw new Error("MAC key already set");
    }

    /* Figure out what to do with the key based on its size relative to
     * the hash's block size */
    if (blockByteSize < key["binLen"] / 8) {
      key["value"] = this.finalizeFunc(
        key["value"],
        key["binLen"],
        0,
        this.newStateFunc(this.shaVariant),
        this.outputBinLen,
      );
    }
    while (key["value"].length <= lastArrayIndex) {
      key["value"].push(0);
    }
    /* Create ipad and opad */
    for (i = 0; i <= lastArrayIndex; i += 1) {
      this.keyWithIPad[i] = key["value"][i] ^ 0x36363636;
      this.keyWithOPad[i] = key["value"][i] ^ 0x5c5c5c5c;
    }

    this.intermediateState = this.roundFunc(this.keyWithIPad, this.intermediateState);
    this.processedLen = this.variantBlockSize;

    this.macKeySet = true;
  }

  /**
   * Returns the the HMAC in the specified format using the key given by a previous `setHMACKey` call.
   *
   * @param format The desired output formatting.
   * @param options Hashmap of extra outputs options.
   * @returns The HMAC in the format specified.
   */
  getHMAC(format: "HEX", options?: { outputUpper?: boolean }): string;
  getHMAC(format: "B64", options?: { b64Pad?: string }): string;
  getHMAC(format: "BYTES"): string;
  getHMAC(format: "UINT8ARRAY"): Uint8Array;
  getHMAC(format: "ARRAYBUFFER"): ArrayBuffer;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  getHMAC(format: any, options?: any): any {
    const outputOptions = getOutputOpts(options),
      formatFunc = getOutputConverter(format, this.outputBinLen, this.bigEndianMod, outputOptions);

    return formatFunc(this._getHMAC());
  }

  /**
   * Internal function that returns the "raw" HMAC
   */
  protected _getHMAC(): number[] {
    let finalizedState;

    if (!this.macKeySet) {
      throw new Error("Cannot call getHMAC without first setting MAC key");
    }

    const firstHash = this.finalizeFunc(
      this.remainder.slice(),
      this.remainderLen,
      this.processedLen,
      this.stateCloneFunc(this.intermediateState),
      this.outputBinLen,
    );
    finalizedState = this.roundFunc(this.keyWithOPad, this.newStateFunc(this.shaVariant));
    finalizedState = this.finalizeFunc(
      firstHash,
      this.outputBinLen,
      this.variantBlockSize,
      finalizedState,
      this.outputBinLen,
    );

    return finalizedState;
  }
}

[evidence record sha256:b995c7320ec2d69083165dbef3b4a88dc64f92b6ea3fc9b1608e4ed09e2d8b00 kind tool-call:read]
step 5: calling local:qwen3.6:35b-a3b
model error (retrying): the model spent all 8192 output tokens without emitting text or a tool call
tool read <- {"path":"src/sha3.ts"}
tool read ok: import { jsSHABase, packedLEConcat, sha_variant_error, mac_rounds_error, TWO_PWR_32, parseInputOption } from "./common";
import {
  packedValue,
  CSHAKEOptionsNoEncodingType,
  CSHAKEOptionsEncodingType,
  SHAKEOptionsNoEncodingType,
  SHAKEOptionsEncodingType,
  KMACOptionsNoEncodingType,
  KMACOptionsEncodingType,
  FixedLengthOptionsEncodingType,
  FixedLengthOptionsNoEncodingType,
  FormatNoTextType,
  ResolvedCSHAKEOptionsNoEncodingType,
  ResolvedKMACOptionsNoEncodingType,
} from "./custom_types";
import { getStrConverter } from "./converters";
import { Int_64, rotl_64, xor_64_2, xor_64_5 } from "./primitives_64";

type FixedLengthVariantType = "SHA3-224" | "SHA3-256" | "SHA3-384" | "SHA3-512" | "SHAKE128" | "SHAKE256";

type VariantType = FixedLengthVariantType | "SHAKE128" | "SHAKE256" | "CSHAKE128" | "CSHAKE256" | "KMAC128" | "KMAC256";

const rc_sha3 = [
  new Int_64(0x00000000, 0x00000001),
  new Int_64(0x00000000, 0x00008082),
  new Int_64(0x80000000, 0x0000808a),
  new Int_64(0x80000000, 0x80008000),
  new Int_64(0x00000000, 0x0000808b),
  new Int_64(0x00000000, 0x80000001),
  new Int_64(0x80000000, 0x80008081),
  new Int_64(0x80000000, 0x00008009),
  new Int_64(0x00000000, 0x0000008a),
  new Int_64(0x00000000, 0x00000088),
  new Int_64(0x00000000, 0x80008009),
  new Int_64(0x00000000, 0x8000000a),
  new Int_64(0x00000000, 0x8000808b),
  new Int_64(0x80000000, 0x0000008b),
  new Int_64(0x80000000, 0x00008089),
  new Int_64(0x80000000, 0x00008003),
  new Int_64(0x80000000, 0x00008002),
  new Int_64(0x80000000, 0x00000080),
  new Int_64(0x00000000, 0x0000800a),
  new Int_64(0x80000000, 0x8000000a),
  new Int_64(0x80000000, 0x80008081),
  new Int_64(0x80000000, 0x00008080),
  new Int_64(0x00000000, 0x80000001),
  new Int_64(0x80000000, 0x80008008),
];

const r_sha3 = [
  [0, 36, 3, 41, 18],
  [1, 44, 10, 45, 2],
  [62, 6, 43, 15, 61],
  [28, 55, 25, 21, 56],
  [27, 20, 39, 8, 14],
];

/**
 * Gets the state values for the specified SHA-3 variant.
 *
 * @param _variant Unused for this family.
 * @returns The initial state values.
 */
function getNewState(_variant: VariantType): Int_64[][] {
  let i;
  const retVal = [];

  for (i = 0; i < 5; i += 1) {
    retVal[i] = [new Int_64(0, 0), new Int_64(0, 0), new Int_64(0, 0), new Int_64(0, 0), new Int_64(0, 0)];
  }

  return retVal;
}

/**
 * Returns a clone of the given SHA3 state.
 *
 * @param state The state to be cloned.
 * @returns The cloned state.
 */
function cloneSHA3State(state: Int_64[][]): Int_64[][] {
  let i;
  const clone = [];
  for (i = 0; i < 5; i += 1) {
    clone[i] = state[i].slice();
  }

  return clone;
}

/**
 * Performs a round of SHA-3 hashing over a block. This clobbers `state`.
 *
 * @param block The binary array representation of the block to hash.
 * @param state Hash state from a previous round.
 * @returns The resulting state value.
 */
function roundSHA3(block: number[] | null, state: Int_64[][]): Int_64[][] {
  let round, x, y, B;
  const C = [],
    D = [];

  if (null !== block) {
    for (x = 0; x < block.length; x += 2) {
      state[(x >>> 1) % 5][((x >>> 1) / 5) | 0] = xor_64_2(
        state[(x >>> 1) % 5][((x >>> 1) / 5) | 0],
        new Int_64(block[x + 1], block[x]),
      );
    }
  }

  for (round = 0; round < 24; round += 1) {
    /* Any SHA-3 variant name will do here */
    B = getNewState("SHA3-384");

    /* Perform theta step */
    for (x = 0; x < 5; x += 1) {
      C[x] = xor_64_5(state[x][0], state[x][1], state[x][2], state[x][3], state[x][4]);
    }
    for (x = 0; x < 5; x += 1) {
      D[x] = xor_64_2(C[(x + 4) % 5], rotl_64(C[(x + 1) % 5], 1));
    }
    for (x = 0; x < 5; x += 1) {
      for (y = 0; y < 5; y += 1) {
        state[x][y] = xor_64_2(state[x][y], D[x]);
      }
    }

    /* Perform combined ro and pi steps */
    for (x = 0; x < 5; x += 1) {
      for (y = 0; y < 5; y += 1) {
        B[y][(2 * x + 3 * y) % 5] = rotl_64(state[x][y], r_sha3[x][y]);
      }
    }

    /* Perform chi step */
    for (x = 0; x < 5; x += 1) {
      for (y = 0; y < 5; y += 1) {
        state[x][y] = xor_64_2(
          B[x][y],
          new Int_64(
            ~B[(x + 1) % 5][y].highOrder & B[(x + 2) % 5][y].highOrder,
            ~B[(x + 1) % 5][y].lowOrder & B[(x + 2) % 5][y].lowOrder,
          ),
        );
      }
    }

    /* Perform iota step */
    state[0][0] = xor_64_2(state[0][0], rc_sha3[round]);
  }

  return state;
}

/**
 * Finalizes the SHA-3 hash. This clobbers `remainder` and `state`.
 *
 * @param remainder Any leftover unprocessed packed ints that still need to be processed.
 * @param remainderBinLen The number of bits in `remainder`.
 * @param _processedBinLen Unused for this family.
 * @param state The state from a previous round.
 * @param blockSize The block size/rate of the variant in bits
 * @param delimiter The delimiter value for the variant
 * @param outputLen The output length for the variant in bits
 * @returns The array of integers representing the SHA-3 hash of message.
 */
function finalizeSHA3(
  remainder: number[],
  remainderBinLen: number,
  _processedBinLen: number,
  state: Int_64[][],
  blockSize: number,
  delimiter: number,
  outputLen: number,
): number[] {
  let i,
    state_offset = 0,
    temp;
  const retVal = [],
    binaryStringInc = blockSize >>> 5,
    remainderIntLen = remainderBinLen >>> 5;

  /* Process as many blocks as possible, some may be here for multiple rounds
		with SHAKE
	*/
  for (i = 0; i < remainderIntLen && remainderBinLen >= blockSize; i += binaryStringInc) {
    state = roundSHA3(remainder.slice(i, i + binaryStringInc), state);
    remainderBinLen -= blockSize;
  }

  remainder = remainder.slice(i);
  remainderBinLen = remainderBinLen % blockSize;

  /* Pad out the remainder to a full block */
  while (remainder.length < binaryStringInc) {
    remainder.push(0);
  }

  /* Find the next "empty" byte for the 0x80 and append it via an xor */
  i = remainderBinLen >>> 3;
  remainder[i >> 2] ^= delimiter << (8 * (i % 4));

  remainder[binaryStringInc - 1] ^= 0x80000000;
  state = roundSHA3(remainder, state);

  while (retVal.length * 32 < outputLen) {
    temp = state[state_offset % 5][(state_offset / 5) | 0];
    retVal.push(temp.lowOrder);
    if (retVal.length * 32 >= outputLen) {
      break;
    }
    retVal.push(temp.highOrder);
    state_offset += 1;

    if (0 === (state_offset * 64) % blockSize) {
      roundSHA3(null, state);
      state_offset = 0;
    }
  }

  return retVal;
}

/**
 * Performs NIST left_encode function returned with no extra garbage bits. `x` is limited to <= 9007199254740991.
 *
 * @param x 32-bit number to to encode.
 * @returns The NIST specified output of the function.
 */
function left_encode(x: number): packedValue {
  let byteOffset,
    byte,
    numEncodedBytes = 0;
  /* JavaScript numbers max out at 0x1FFFFFFFFFFFFF (7 bytes) so this will return a maximum of 7 + 1 = 8 bytes */
  const retVal = [0, 0],
    x_64 = [x & 0xffffffff, (x / TWO_PWR_32) & 0x1fffff];

  for (byteOffset = 6; byteOffset >= 0; byteOffset--) {
    /* This will surprisingly work for large shifts because JavaScript masks the shift amount by 0x1F */
    byte = (x_64[byteOffset >> 2] >>> (8 * byteOffset)) & 0xff;

    /* Starting from the most significant byte of a 64-bit number, start recording the first non-0 byte and then
       every byte thereafter */
    if (byte !== 0 || numEncodedBytes !== 0) {
      retVal[(numEncodedBytes + 1) >> 2] |= byte << ((numEncodedBytes + 1) * 8);
      numEncodedBytes += 1;
    }
  }
  numEncodedBytes = numEncodedBytes !== 0 ? numEncodedBytes : 1;
  retVal[0] |= numEncodedBytes;

  return { value: numEncodedBytes + 1 > 4 ? retVal : [retVal[0]], binLen: 8 + numEncodedBytes * 8 };
}

/**
 * Performs NIST right_encode function returned with no extra garbage bits. `x` is limited to <= 9007199254740991.
 *
 * @param x 32-bit number to to encode.
 * @returns The NIST specified output of the function.
 */
function right_encode(x: number): packedValue {
  let byteOffset,
    byte,
    numEncodedBytes = 0;
  /* JavaScript numbers max out at 0x1FFFFFFFFFFFFF (7 bytes) so this will return a maximum of 7 + 1 = 8 bytes */
  const retVal = [0, 0],
    x_64 = [x & 0xffffffff, (x / TWO_PWR_32) & 0x1fffff];

  for (byteOffset = 6; byteOffset >= 0; byteOffset--) {
    /* This will surprisingly work for large shifts because JavaScript masks the shift amount by 0x1F */
    byte = (x_64[byteOffset >> 2] >>> (8 * byteOffset)) & 0xff;

    /* Starting from the most significant byte of a 64-bit number, start recording the first non-0 byte and then
       every byte thereafter */
    if (byte !== 0 || numEncodedBytes !== 0) {
      retVal[numEncodedBytes >> 2] |= byte << (numEncodedBytes * 8);
      numEncodedBytes += 1;
    }
  }
  numEncodedBytes = numEncodedBytes !== 0 ? numEncodedBytes : 1;
  retVal[numEncodedBytes >> 2] |= numEncodedBytes << (numEncodedBytes * 8);

  return { value: numEncodedBytes + 1 > 4 ? retVal : [retVal[0]], binLen: 8 + numEncodedBytes * 8 };
}

/**
 * Performs NIST encode_string function.
 *
 * @param input Packed array of integers.
 * @returns NIST encode_string output.
 */
function encode_string(input: packedValue): packedValue {
  return packedLEConcat(left_encode(input["binLen"]), input);
}

/**
 * Performs NIST byte_pad function.
 *
 * @param packed Packed array of integers.
 * @param outputByteLen Desired length of the output in bytes, assumed to be a multiple of 4.
 * @returns NIST byte_pad output.
 */
function byte_pad(packed: packedValue, outputByteLen: number): number[] {
  let encodedLen = left_encode(outputByteLen),
    i;

  encodedLen = packedLEConcat(encodedLen, packed);
  const outputIntLen = outputByteLen >>> 2,
    intsToAppend = (outputIntLen - (encodedLen["value"].length % outputIntLen)) % outputIntLen;

  for (i = 0; i < intsToAppend; i++) {
    encodedLen["value"].push(0);
  }

  return encodedLen["value"];
}

/**
 * Parses/validate constructor options for a CSHAKE variant
 *
 * @param options Option given to constructor
 */
function resolveCSHAKEOptions(options: CSHAKEOptionsNoEncodingType): ResolvedCSHAKEOptionsNoEncodingType {
  const resolvedOptions = options || {};

  return {
    funcName: parseInputOption("funcName", resolvedOptions["funcName"], 1, { value: [], binLen: 0 }),
    customization: parseInputOption("Customization", resolvedOptions["customization"], 1, { value: [], binLen: 0 }),
  };
}

/**
 * Parses/validate constructor options for a KMAC variant
 *
 * @param options Option given to constructor
 */
function resolveKMACOptions(options: KMACOptionsNoEncodingType): ResolvedKMACOptionsNoEncodingType {
  const resolvedOptions = options || {};

  return {
    kmacKey: parseInputOption("kmacKey", resolvedOptions["kmacKey"], 1),
    /* This is little-endian packed "KMAC" */
    funcName: { value: [0x43414d4b], binLen: 32 },
    customization: parseInputOption("Customization", resolvedOptions["customization"], 1, { value: [], binLen: 0 }),
  };
}

export default class jsSHA extends jsSHABase<Int_64[][], VariantType> {
  intermediateState: Int_64[][];
  variantBlockSize: number;
  bigEndianMod: -1 | 1;
  outputBinLen: number;
  isVariableLen: boolean;
  HMACSupported: boolean;

  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  converterFunc: (input: any, existingBin: number[], existingBinLen: number) => packedValue;
  roundFunc: (block: number[], H: Int_64[][]) => Int_64[][];
  finalizeFunc: (
    remainder: number[],
    remainderBinLen: number,
    processedBinLen: number,
    H: Int_64[][],
    outputLen: number,
  ) => number[];
  stateCloneFunc: (state: Int_64[][]) => Int_64[][];
  newStateFunc: (variant: VariantType) => Int_64[][];
  getMAC: ((options: { outputLen: number }) => number[]) | null;

  constructor(variant: FixedLengthVariantType, inputFormat: "TEXT", options?: FixedLengthOptionsEncodingType);
  constructor(
    variant: FixedLengthVariantType,
    inputFormat: FormatNoTextType,
    options?: FixedLengthOptionsNoEncodingType,
  );
  constructor(variant: "SHAKE128" | "SHAKE256", inputFormat: "TEXT", options?: SHAKEOptionsEncodingType);
  constructor(variant: "SHAKE128" | "SHAKE256", inputFormat: FormatNoTextType, options?: SHAKEOptionsNoEncodingType);
  constructor(variant: "CSHAKE128" | "CSHAKE256", inputFormat: "TEXT", options?: CSHAKEOptionsEncodingType);
  constructor(variant: "CSHAKE128" | "CSHAKE256", inputFormat: FormatNoTextType, options?: CSHAKEOptionsNoEncodingType);
  constructor(variant: "KMAC128" | "KMAC256", inputFormat: "TEXT", options: KMACOptionsEncodingType);
  constructor(variant: "KMAC128" | "KMAC256", inputFormat: FormatNoTextType, options: KMACOptionsNoEncodingType);
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  constructor(variant: any, inputFormat: any, options?: any) {
    let delimiter = 0x06,
      variantBlockSize = 0;
    super(variant, inputFormat, options);
    const resolvedOptions = options || {};

    /* In other variants, this was done after variable initialization but need to do it earlier here becaue we want to
       avoid KMAC initialization */
    if (this.numRounds !== 1) {
      if (resolvedOptions["kmacKey"] || resolvedOptions["hmacKey"]) {
        throw new Error(mac_rounds_error);
      } else if (this.shaVariant === "CSHAKE128" || this.shaVariant === "CSHAKE256") {
        throw new Error("Cannot set numRounds for CSHAKE variants");
      }
    }

    this.bigEndianMod = 1;
    this.converterFunc = getStrConverter(this.inputFormat, this.utfType, this.bigEndianMod);
    this.roundFunc = roundSHA3;
    this.stateCloneFunc = cloneSHA3State;
    this.newStateFunc = getNewState;
    this.intermediateState = getNewState(variant);

    this.isVariableLen = false;
    switch (variant) {
      case "SHA3-224":
        this.variantBlockSize = variantBlockSize = 1152;
        this.outputBinLen = 224;
        this.HMACSupported = true;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getHMAC;
        break;
      case "SHA3-256":
        this.variantBlockSize = variantBlockSize = 1088;
        this.outputBinLen = 256;
        this.HMACSupported = true;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getHMAC;
        break;
      case "SHA3-384":
        this.variantBlockSize = variantBlockSize = 832;
        this.outputBinLen = 384;
        this.HMACSupported = true;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getHMAC;
        break;
      case "SHA3-512":
        this.variantBlockSize = variantBlockSize = 576;
        this.outputBinLen = 512;
        this.HMACSupported = true;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getHMAC;
        break;
      case "SHAKE128":
        delimiter = 0x1f;
        this.variantBlockSize = variantBlockSize = 1344;
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        this.getMAC = null;
        break;
      case "SHAKE256":
        delimiter = 0x1f;
        this.variantBlockSize = variantBlockSize = 1088;
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        this.getMAC = null;
        break;
      case "KMAC128":
        delimiter = 0x4;
        this.variantBlockSize = variantBlockSize = 1344;
        this._initializeKMAC(options);
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getKMAC;
        break;
      case "KMAC256":
        delimiter = 0x4;
        this.variantBlockSize = variantBlockSize = 1088;
        this._initializeKMAC(options);
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        this.getMAC = this._getKMAC;
        break;
      case "CSHAKE128":
        this.variantBlockSize = variantBlockSize = 1344;
        delimiter = this._initializeCSHAKE(options);
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        this.getMAC = null;
        break;
      case "CSHAKE256":
        this.variantBlockSize = variantBlockSize = 1088;
        delimiter = this._initializeCSHAKE(options);
        /* This will be set in getHash */
        this.outputBinLen = -1;
        this.isVariableLen = true;
        this.HMACSupported = false;
        this.getMAC = null;
        break;
      default:
        throw new Error(sha_variant_error);
    }

    /* This needs to be down here as CSHAKE can change its delimiter */
    this.finalizeFunc = function (remainder, remainderBinLen, processedBinLen, state, outputBinLen): number[] {
      return finalizeSHA3(
        remainder,
        remainderBinLen,
        processedBinLen,
        state,
        variantBlockSize,
        delimiter,
        outputBinLen,
      );
    };

    if (resolvedOptions["hmacKey"]) {
      this._setHMACKey(parseInputOption("hmacKey", resolvedOptions["hmacKey"], this.bigEndianMod));
    }
  }

  /**
   * Initialize CSHAKE variants.
   *
   * @param options Options containing CSHAKE params.
   * @param funcNameOverride Overrides any "funcName" present in `options` (used with KMAC)
   * @returns The delimiter to be used
   */
  protected _initializeCSHAKE(options?: CSHAKEOptionsNoEncodingType, funcNameOverride?: packedValue): number {
    const resolvedOptions = resolveCSHAKEOptions(options || {});
    if (funcNameOverride) {
      resolvedOptions["funcName"] = funcNameOverride;
    }
    const packedParams = packedLEConcat(
      encode_string(resolvedOptions["funcName"]),
      encode_string(resolvedOptions["customization"]),
    );

    /* CSHAKE is defined to be a call to SHAKE iff both the customization and function-name string are both empty.  This
       can be accomplished by processing nothing in this step. */
    if (resolvedOptions["customization"]["binLen"] !== 0 || resolvedOptions["funcName"]["binLen"] !== 0) {
      const byte_pad_out = byte_pad(packedParams, this.variantBlockSize >>> 3);
      for (let i = 0; i < byte_pad_out.length; i += this.variantBlockSize >>> 5) {
        this.intermediateState = this.roundFunc(
          byte_pad_out.slice(i, i + (this.variantBlockSize >>> 5)),
          this.intermediateState,
        );
        this.processedLen += this.variantBlockSize;
      }
      return 0x04;
    } else {
      return 0x1f;
    }
  }

  /**
   * Initialize KMAC variants.
   *
   * @param options Options containing KMAC params.
   */
  protected _initializeKMAC(options: KMACOptionsNoEncodingType): void {
    const resolvedOptions = resolveKMACOptions(options || {});

    this._initializeCSHAKE(options, resolvedOptions["funcName"]);
    const byte_pad_out = byte_pad(encode_string(resolvedOptions["kmacKey"]), this.variantBlockSize >>> 3);
    for (let i = 0; i < byte_pad_out.length; i += this.variantBlockSize >>> 5) {
      this.intermediateState = this.roundFunc(
        byte_pad_out.slice(i, i + (this.variantBlockSize >>> 5)),
        this.intermediateState,
      );
      this.processedLen += this.variantBlockSize;
    }
    this.macKeySet = true;
  }

  /**
   * Returns the the KMAC in the specified format.
   *
   * @param options Hashmap of extra outputs options. `outputLen` must be specified.
   * @returns The KMAC in the format specified.
   */
  protected _getKMAC(options: { outputLen: number }): number[] {
    const concatedRemainder = packedLEConcat(
      { value: this.remainder.slice(), binLen: this.remainderLen },
      right_encode(options["outputLen"]),
    );

    return this.finalizeFunc(
      concatedRemainder["value"],
      concatedRemainder["binLen"],
      this.processedLen,
      this.stateCloneFunc(this.intermediateState),
      options["outputLen"],
    );
  }
}

[evidence record sha256:fe4f3bfc063c18d5fae64a420b2a282e603dd01397c366c411ae025ac0ce9a75 kind tool-call:read]
step 6: calling local:qwen3.6:35b-a3b
model error (retrying): the model spent all 8192 output tokens without emitting text or a tool call
model error (retrying): the model spent all 8192 output tokens without emitting text or a tool call
stopped: output-cap after 6 steps, 148946 tokens
gate typecheck not-applicable: package.json declares no typecheck script [evidence record sha256:81e5bc34c76ccd066aba574f815550ddb8a53ef241292bcee176472f2a07b09a]
gate lint not-applicable: package.json declares no lint script [evidence record sha256:a42d74a71928cc321753e9ff30d2becf63539b52e76f1be20715a03739e6f953]
gate format not-applicable: package.json declares no check-only format script, and running a writing formatter as a gate would edit the tree it is judging [evidence record sha256:6920e5a176ea5e96d174e1f9daeb0b28dc3ee2ec8a64abe88be1f4c54d7534a6]
gate tests failed: the command exited 30 [evidence record sha256:7dd3cdece0bdcd2dde917aa3ca269e3a9a3c019304970a706c51e17cdc7312f4]
gate file-set passed: nothing changed and no file set was declared, so there is nothing to check [evidence record sha256:8f060c760cb21f6bc53409b9b12c1b89574ec56b9b8479c3289fe47912fe6747]
gate placeholder passed: no placeholder marker was introduced by this change [evidence record sha256:80a69ddf538b2edff3530ed1afc787650dc7d826a04eded035e165077cbe6257]
gate secret-scan passed: no known credential pattern appears in the added lines [evidence record sha256:48401cbfcc12987dfae6c002c84fc54a4e94fc5d6e443f68ed699bb2fc400bbf]
gate behaviour-probe passed: 0 changed function(s) still answer to their inputs. [evidence record sha256:d10ec5b4c8a1d40b28d094707e71408003a41b576e152d86d5eeb98612fa9caa]
gate diff-budget passed (advisory): within budget: 0 file(s) and 0 added line(s) [evidence record sha256:88aa9bd5ad9d2a92e5841f09ea57898fd1525c205fa2ffd68ed0e20022bdd6f8]
ratchet accepted attempt 2: the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage) [evidence record sha256:1ec96f24a6f1ebee6a41fdb93ecc92ec0156c783a5c3052e24f6f2a36df79a93]
escalated after 2 attempt(s) at gate tests: the command exited 30

no files were changed. The gates below measured an unchanged workspace, so they say nothing about work being done.

gates:
  n/a      typecheck: package.json declares no typecheck script
  n/a      lint: package.json declares no lint script
  n/a      format: package.json declares no check-only format script, and running a writing formatter as a gate would edit the tree it is judging
  failed   tests: the command exited 30
  passed   file-set: nothing changed and no file set was declared, so there is nothing to check
  passed   placeholder: no placeholder marker was introduced by this change
  passed   secret-scan: no known credential pattern appears in the added lines
  passed   behaviour-probe: 0 changed function(s) still answer to their inputs.
  passed   diff-budget (advisory): within budget: 0 file(s) and 0 added line(s)
attempt 1: accepted - the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage)
attempt 2: accepted - the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage)

Escalating after 2 of 2 attempts.

Gate: tests (tests (npm run test))
Why: the command exited 30
Its last run is ledger record sha256:7dd3cdece0bdcd2dde917aa3ca269e3a9a3c019304970a706c51e17cdc7312f4.

Attempts:
  1. accepted - the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage)
     still failing: tests
  2. accepted - the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage)
     still failing: tests

routing reward: 0.000 (the run escalated, so the gates never went green)
[signing] the Secret Service keyring would not take a new key (secret-tool store failed: ), so the bundle is signed with a per-run key

evidence bundle: /out/bundle
verify it anywhere: node /out/bundle/verify.mjs /out/bundle
review it: open /out/bundle/review.html
what this run produced

  the page a person reads: /out/bundle/review.html
  the bundle a stranger verifies: /out/bundle
  its own verifier, needing nothing installed: node /out/bundle/verify.mjs /out/bundle
  the chain every record is on: /out/bundle/ledger.jsonl

  133 records. The harness verified 1 claim(s) and refused 0.
  bundle verified in this run: verify.mjs exited 0
[chokepoint] refusing shell without a terminal to confirm on: "cd /work && cat test/src/test_converters.ts | head -80" is not on the shell allowlist.
[chokepoint] refusing shell without a terminal to confirm on: "cd /work && cat src/sha3.ts | grep -n "packedLEConcat" | head -20" is not on the shell allowlist.
[chokepoint] refusing shell without a terminal to confirm on: "cd /work && cat test/src/test_common.ts | grep -n "packedLEConcat" -A 5 | head -60" is not on the shell allowlist.
