From fa219d96f16358bfe64721f4be0885adb51c4800 Mon Sep 17 00:00:00 2001 From: Thomas M <44269971+thomasxm@users.noreply.github.com> Date: Sun, 5 Jul 2026 11:11:31 +0100 Subject: [PATCH 01/25] feat: add PRESENT and Twofish ciphers (#2157) --- src/core/config/Categories.json | 4 + src/core/lib/Present.mjs | 422 +++++++++++++++++ src/core/lib/Twofish.mjs | 608 +++++++++++++++++++++++++ src/core/operations/PRESENTDecrypt.mjs | 94 ++++ src/core/operations/PRESENTEncrypt.mjs | 94 ++++ src/core/operations/TwofishDecrypt.mjs | 94 ++++ src/core/operations/TwofishEncrypt.mjs | 94 ++++ tests/operations/tests/PRESENT.mjs | 465 +++++++++++++++++++ tests/operations/tests/Twofish.mjs | 486 ++++++++++++++++++++ 9 files changed, 2361 insertions(+) create mode 100644 src/core/lib/Present.mjs create mode 100644 src/core/lib/Twofish.mjs create mode 100644 src/core/operations/PRESENTDecrypt.mjs create mode 100644 src/core/operations/PRESENTEncrypt.mjs create mode 100644 src/core/operations/TwofishDecrypt.mjs create mode 100644 src/core/operations/TwofishEncrypt.mjs create mode 100644 tests/operations/tests/PRESENT.mjs create mode 100644 tests/operations/tests/Twofish.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index a417af77..8c64c398 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -115,6 +115,10 @@ "RC6 Decrypt", "Ascon Encrypt", "Ascon Decrypt", + "PRESENT Encrypt", + "PRESENT Decrypt", + "Twofish Encrypt", + "Twofish Decrypt", "GOST Encrypt", "GOST Decrypt", "GOST Sign", diff --git a/src/core/lib/Present.mjs b/src/core/lib/Present.mjs new file mode 100644 index 00000000..ffb6bf57 --- /dev/null +++ b/src/core/lib/Present.mjs @@ -0,0 +1,422 @@ +/** + * Complete implementation of PRESENT block cipher encryption/decryption with + * ECB and CBC block modes. + * + * PRESENT is an ultra-lightweight block cipher designed for constrained environments. + * Standardised in ISO/IEC 29192-2:2019. + * + * Reference: "PRESENT: An Ultra-Lightweight Block Cipher" + * https://link.springer.com/chapter/10.1007/978-3-540-74735-2_31 + * + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; + +/** Number of rounds */ +const NROUNDS = 31; + +/** Block size in bytes (64 bits) */ +const BLOCKSIZE = 8; + +/** The 4-bit S-box (16 values) */ +const SBOX = [ + 0xC, 0x5, 0x6, 0xB, 0x9, 0x0, 0xA, 0xD, + 0x3, 0xE, 0xF, 0x8, 0x4, 0x7, 0x1, 0x2 +]; + +/** Inverse S-box for decryption */ +const SBOX_INV = [ + 0x5, 0xE, 0xF, 0x8, 0xC, 0x1, 0x2, 0xD, + 0xB, 0x4, 0x6, 0x3, 0x0, 0x7, 0x9, 0xA +]; + +/** P-layer permutation table (bit i goes to position P[i]) */ +const PBOX = [ + 0, 16, 32, 48, 1, 17, 33, 49, 2, 18, 34, 50, 3, 19, 35, 51, + 4, 20, 36, 52, 5, 21, 37, 53, 6, 22, 38, 54, 7, 23, 39, 55, + 8, 24, 40, 56, 9, 25, 41, 57, 10, 26, 42, 58, 11, 27, 43, 59, + 12, 28, 44, 60, 13, 29, 45, 61, 14, 30, 46, 62, 15, 31, 47, 63 +]; + +/** Inverse P-layer permutation for decryption */ +const PBOX_INV = new Array(64); +for (let i = 0; i < 64; i++) { + PBOX_INV[PBOX[i]] = i; +} + +/** + * Convert byte array to BigInt (big-endian) + * @param {number[]} bytes - Array of bytes + * @returns {bigint} - 64-bit value as BigInt + */ +function bytesToBigInt(bytes) { + let result = 0n; + for (let i = 0; i < bytes.length; i++) { + result = (result << 8n) | BigInt(bytes[i]); + } + return result; +} + +/** + * Convert BigInt to byte array (big-endian) + * @param {bigint} value - BigInt value + * @param {number} length - Desired byte array length + * @returns {number[]} - Array of bytes + */ +function bigIntToBytes(value, length) { + const bytes = []; + for (let i = length - 1; i >= 0; i--) { + bytes[i] = Number(value & 0xFFn); + value >>= 8n; + } + return bytes; +} + +/** + * Apply S-box substitution layer to 64-bit state + * @param {bigint} state - 64-bit state + * @param {number[]} sbox - S-box to use + * @returns {bigint} - Substituted state + */ +function sBoxLayer(state, sbox) { + let result = 0n; + for (let i = 0; i < 16; i++) { + const nibble = Number((state >> BigInt(i * 4)) & 0xFn); + result |= BigInt(sbox[nibble]) << BigInt(i * 4); + } + return result; +} + +/** + * Apply P-layer permutation to 64-bit state + * @param {bigint} state - 64-bit state + * @param {number[]} pbox - Permutation table to use + * @returns {bigint} - Permuted state + */ +function pLayer(state, pbox) { + let result = 0n; + for (let i = 0; i < 64; i++) { + if ((state >> BigInt(i)) & 1n) { + result |= 1n << BigInt(pbox[i]); + } + } + return result; +} + +/** + * Generate round keys for 80-bit key + * @param {number[]} key - 10-byte key + * @returns {bigint[]} - Array of 32 round keys (64-bit each) + */ +function generateRoundKeys80(key) { + // Key register is 80 bits + let keyReg = bytesToBigInt(key); + const roundKeys = []; + + for (let i = 1; i <= NROUNDS + 1; i++) { + // Extract round key (leftmost 64 bits) + roundKeys.push(keyReg >> 16n); + + // Rotate left by 61 positions + keyReg = ((keyReg << 61n) | (keyReg >> 19n)) & ((1n << 80n) - 1n); + + // Apply S-box to leftmost 4 bits + const leftNibble = Number(keyReg >> 76n); + keyReg = (keyReg & ((1n << 76n) - 1n)) | (BigInt(SBOX[leftNibble]) << 76n); + + // XOR round counter to bits 19-15 + keyReg ^= BigInt(i) << 15n; + } + + return roundKeys; +} + +/** + * Generate round keys for 128-bit key + * @param {number[]} key - 16-byte key + * @returns {bigint[]} - Array of 32 round keys (64-bit each) + */ +function generateRoundKeys128(key) { + // Key register is 128 bits + let keyReg = bytesToBigInt(key); + const roundKeys = []; + + for (let i = 1; i <= NROUNDS + 1; i++) { + // Extract round key (leftmost 64 bits) + roundKeys.push(keyReg >> 64n); + + // Rotate left by 61 positions + keyReg = ((keyReg << 61n) | (keyReg >> 67n)) & ((1n << 128n) - 1n); + + // Apply S-box to leftmost 8 bits (two nibbles: bits 127-124 and 123-120) + const leftByte = Number((keyReg >> 120n) & 0xFFn); + const leftNibble1 = (leftByte >> 4) & 0xF; // bits 127-124 + const leftNibble2 = leftByte & 0xF; // bits 123-120 + keyReg = (keyReg & ((1n << 120n) - 1n)) | + (BigInt((SBOX[leftNibble1] << 4) | SBOX[leftNibble2]) << 120n); + + // XOR round counter to bits 66-62 + keyReg ^= BigInt(i) << 62n; + } + + return roundKeys; +} + +/** + * Encrypt a single 64-bit block + * @param {bigint} block - 64-bit plaintext block + * @param {bigint[]} roundKeys - Round keys + * @returns {bigint} - 64-bit ciphertext block + */ +function encryptBlock(block, roundKeys) { + let state = block; + + for (let i = 0; i < NROUNDS; i++) { + // Add round key + state ^= roundKeys[i]; + // S-box layer + state = sBoxLayer(state, SBOX); + // P-layer + state = pLayer(state, PBOX); + } + + // Final round key addition + state ^= roundKeys[NROUNDS]; + + return state; +} + +/** + * Decrypt a single 64-bit block + * @param {bigint} block - 64-bit ciphertext block + * @param {bigint[]} roundKeys - Round keys + * @returns {bigint} - 64-bit plaintext block + */ +function decryptBlock(block, roundKeys) { + let state = block; + + // Reverse key addition + state ^= roundKeys[NROUNDS]; + + for (let i = NROUNDS - 1; i >= 0; i--) { + // Inverse P-layer + state = pLayer(state, PBOX_INV); + // Inverse S-box layer + state = sBoxLayer(state, SBOX_INV); + // Add round key + state ^= roundKeys[i]; + } + + return state; +} + +/** + * Apply padding to message + * @param {number[]} message - Original message + * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT") + * @param {number} blockSize - Block size in bytes + * @returns {number[]} - Padded message + */ +function applyPadding(message, padding, blockSize) { + const remainder = message.length % blockSize; + let nPadding = remainder === 0 ? 0 : blockSize - remainder; + + // For PKCS5, always add at least one byte (full block if already aligned) + if (padding === "PKCS5" && remainder === 0) { + nPadding = blockSize; + } + + if (nPadding === 0) return [...message]; + + const paddedMessage = [...message]; + + switch (padding) { + case "NO": + throw new OperationError(`No padding requested but input is not a ${blockSize}-byte multiple.`); + + case "PKCS5": + for (let i = 0; i < nPadding; i++) { + paddedMessage.push(nPadding); + } + break; + + case "ZERO": + for (let i = 0; i < nPadding; i++) { + paddedMessage.push(0); + } + break; + + case "RANDOM": + for (let i = 0; i < nPadding; i++) { + paddedMessage.push(Math.floor(Math.random() * 256)); + } + break; + + case "BIT": + paddedMessage.push(0x80); + for (let i = 1; i < nPadding; i++) { + paddedMessage.push(0); + } + break; + + default: + throw new OperationError(`Unknown padding type: ${padding}`); + } + + return paddedMessage; +} + +/** + * Remove padding from message + * @param {number[]} message - Padded message + * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT") + * @param {number} blockSize - Block size in bytes + * @returns {number[]} - Unpadded message + */ +function removePadding(message, padding, blockSize) { + if (message.length === 0) return message; + + switch (padding) { + case "NO": + case "ZERO": + case "RANDOM": + // These padding types cannot be reliably removed + return message; + + case "PKCS5": { + const padByte = message[message.length - 1]; + if (padByte > 0 && padByte <= blockSize) { + // Verify padding + for (let i = 0; i < padByte; i++) { + if (message[message.length - 1 - i] !== padByte) { + throw new OperationError("Invalid PKCS#5 padding."); + } + } + return message.slice(0, message.length - padByte); + } + throw new OperationError("Invalid PKCS#5 padding."); + } + + case "BIT": { + // Find 0x80 byte working backwards, skipping zeros + for (let i = message.length - 1; i >= 0; i--) { + if (message[i] === 0x80) { + return message.slice(0, i); + } else if (message[i] !== 0) { + throw new OperationError("Invalid BIT padding."); + } + } + throw new OperationError("Invalid BIT padding."); + } + + default: + throw new OperationError(`Unknown padding type: ${padding}`); + } +} + +/** + * Encrypt using PRESENT cipher with specified block mode + * + * @param {number[]} message - Plaintext as byte array + * @param {number[]} key - Key (10 bytes for 80-bit or 16 bytes for 128-bit) + * @param {number[]} iv - IV (8 bytes, not used for ECB) + * @param {string} mode - Block cipher mode ("ECB" or "CBC") + * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT") + * @returns {number[]} - Ciphertext as byte array + */ +export function encryptPRESENT(message, key, iv, mode = "ECB", padding = "PKCS5") { + if (message.length === 0) return []; + + // Generate round keys based on key length + const roundKeys = key.length === 10 ? + generateRoundKeys80(key) : + generateRoundKeys128(key); + + // Apply padding + const paddedMessage = applyPadding(message, padding, BLOCKSIZE); + + const cipherText = []; + + switch (mode) { + case "ECB": + for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) { + const block = bytesToBigInt(paddedMessage.slice(i, i + BLOCKSIZE)); + const encrypted = encryptBlock(block, roundKeys); + cipherText.push(...bigIntToBytes(encrypted, BLOCKSIZE)); + } + break; + + case "CBC": { + let ivBlock = bytesToBigInt(iv); + for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) { + let block = bytesToBigInt(paddedMessage.slice(i, i + BLOCKSIZE)); + block ^= ivBlock; + const encrypted = encryptBlock(block, roundKeys); + cipherText.push(...bigIntToBytes(encrypted, BLOCKSIZE)); + ivBlock = encrypted; + } + break; + } + + default: + throw new OperationError(`Invalid block cipher mode: ${mode}`); + } + + return cipherText; +} + +/** + * Decrypt using PRESENT cipher with specified block mode + * + * @param {number[]} cipherText - Ciphertext as byte array + * @param {number[]} key - Key (10 bytes for 80-bit or 16 bytes for 128-bit) + * @param {number[]} iv - IV (8 bytes, not used for ECB) + * @param {string} mode - Block cipher mode ("ECB" or "CBC") + * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT") + * @returns {number[]} - Plaintext as byte array + */ +export function decryptPRESENT(cipherText, key, iv, mode = "ECB", padding = "PKCS5") { + if (cipherText.length === 0) return []; + + if (cipherText.length % BLOCKSIZE !== 0) { + throw new OperationError(`Invalid ciphertext length: ${cipherText.length} bytes. Must be a multiple of 8.`); + } + + // Generate round keys based on key length + const roundKeys = key.length === 10 ? + generateRoundKeys80(key) : + generateRoundKeys128(key); + + const plainText = []; + + switch (mode) { + case "ECB": + for (let i = 0; i < cipherText.length; i += BLOCKSIZE) { + const block = bytesToBigInt(cipherText.slice(i, i + BLOCKSIZE)); + const decrypted = decryptBlock(block, roundKeys); + plainText.push(...bigIntToBytes(decrypted, BLOCKSIZE)); + } + break; + + case "CBC": { + let ivBlock = bytesToBigInt(iv); + for (let i = 0; i < cipherText.length; i += BLOCKSIZE) { + const block = bytesToBigInt(cipherText.slice(i, i + BLOCKSIZE)); + let decrypted = decryptBlock(block, roundKeys); + decrypted ^= ivBlock; + plainText.push(...bigIntToBytes(decrypted, BLOCKSIZE)); + ivBlock = block; + } + break; + } + + default: + throw new OperationError(`Invalid block cipher mode: ${mode}`); + } + + // Remove padding + return removePadding(plainText, padding, BLOCKSIZE); +} diff --git a/src/core/lib/Twofish.mjs b/src/core/lib/Twofish.mjs new file mode 100644 index 00000000..2654df40 --- /dev/null +++ b/src/core/lib/Twofish.mjs @@ -0,0 +1,608 @@ +/** + * Complete implementation of Twofish block cipher encryption/decryption with + * ECB, CBC, CFB, OFB, CTR block modes. + * + * Twofish was an AES finalist designed by Bruce Schneier et al. + * Reference: https://www.schneier.com/academic/twofish/ + * + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; + +/** Number of rounds */ +const NROUNDS = 16; + +/** Block size in bytes (128 bits) */ +const BLOCKSIZE = 16; + +/** Q0 permutation */ +const Q0 = [ + 0xa9, 0x67, 0xb3, 0xe8, 0x04, 0xfd, 0xa3, 0x76, 0x9a, 0x92, 0x80, 0x78, 0xe4, 0xdd, 0xd1, 0x38, + 0x0d, 0xc6, 0x35, 0x98, 0x18, 0xf7, 0xec, 0x6c, 0x43, 0x75, 0x37, 0x26, 0xfa, 0x13, 0x94, 0x48, + 0xf2, 0xd0, 0x8b, 0x30, 0x84, 0x54, 0xdf, 0x23, 0x19, 0x5b, 0x3d, 0x59, 0xf3, 0xae, 0xa2, 0x82, + 0x63, 0x01, 0x83, 0x2e, 0xd9, 0x51, 0x9b, 0x7c, 0xa6, 0xeb, 0xa5, 0xbe, 0x16, 0x0c, 0xe3, 0x61, + 0xc0, 0x8c, 0x3a, 0xf5, 0x73, 0x2c, 0x25, 0x0b, 0xbb, 0x4e, 0x89, 0x6b, 0x53, 0x6a, 0xb4, 0xf1, + 0xe1, 0xe6, 0xbd, 0x45, 0xe2, 0xf4, 0xb6, 0x66, 0xcc, 0x95, 0x03, 0x56, 0xd4, 0x1c, 0x1e, 0xd7, + 0xfb, 0xc3, 0x8e, 0xb5, 0xe9, 0xcf, 0xbf, 0xba, 0xea, 0x77, 0x39, 0xaf, 0x33, 0xc9, 0x62, 0x71, + 0x81, 0x79, 0x09, 0xad, 0x24, 0xcd, 0xf9, 0xd8, 0xe5, 0xc5, 0xb9, 0x4d, 0x44, 0x08, 0x86, 0xe7, + 0xa1, 0x1d, 0xaa, 0xed, 0x06, 0x70, 0xb2, 0xd2, 0x41, 0x7b, 0xa0, 0x11, 0x31, 0xc2, 0x27, 0x90, + 0x20, 0xf6, 0x60, 0xff, 0x96, 0x5c, 0xb1, 0xab, 0x9e, 0x9c, 0x52, 0x1b, 0x5f, 0x93, 0x0a, 0xef, + 0x91, 0x85, 0x49, 0xee, 0x2d, 0x4f, 0x8f, 0x3b, 0x47, 0x87, 0x6d, 0x46, 0xd6, 0x3e, 0x69, 0x64, + 0x2a, 0xce, 0xcb, 0x2f, 0xfc, 0x97, 0x05, 0x7a, 0xac, 0x7f, 0xd5, 0x1a, 0x4b, 0x0e, 0xa7, 0x5a, + 0x28, 0x14, 0x3f, 0x29, 0x88, 0x3c, 0x4c, 0x02, 0xb8, 0xda, 0xb0, 0x17, 0x55, 0x1f, 0x8a, 0x7d, + 0x57, 0xc7, 0x8d, 0x74, 0xb7, 0xc4, 0x9f, 0x72, 0x7e, 0x15, 0x22, 0x12, 0x58, 0x07, 0x99, 0x34, + 0x6e, 0x50, 0xde, 0x68, 0x65, 0xbc, 0xdb, 0xf8, 0xc8, 0xa8, 0x2b, 0x40, 0xdc, 0xfe, 0x32, 0xa4, + 0xca, 0x10, 0x21, 0xf0, 0xd3, 0x5d, 0x0f, 0x00, 0x6f, 0x9d, 0x36, 0x42, 0x4a, 0x5e, 0xc1, 0xe0 +]; + +/** Q1 permutation */ +const Q1 = [ + 0x75, 0xf3, 0xc6, 0xf4, 0xdb, 0x7b, 0xfb, 0xc8, 0x4a, 0xd3, 0xe6, 0x6b, 0x45, 0x7d, 0xe8, 0x4b, + 0xd6, 0x32, 0xd8, 0xfd, 0x37, 0x71, 0xf1, 0xe1, 0x30, 0x0f, 0xf8, 0x1b, 0x87, 0xfa, 0x06, 0x3f, + 0x5e, 0xba, 0xae, 0x5b, 0x8a, 0x00, 0xbc, 0x9d, 0x6d, 0xc1, 0xb1, 0x0e, 0x80, 0x5d, 0xd2, 0xd5, + 0xa0, 0x84, 0x07, 0x14, 0xb5, 0x90, 0x2c, 0xa3, 0xb2, 0x73, 0x4c, 0x54, 0x92, 0x74, 0x36, 0x51, + 0x38, 0xb0, 0xbd, 0x5a, 0xfc, 0x60, 0x62, 0x96, 0x6c, 0x42, 0xf7, 0x10, 0x7c, 0x28, 0x27, 0x8c, + 0x13, 0x95, 0x9c, 0xc7, 0x24, 0x46, 0x3b, 0x70, 0xca, 0xe3, 0x85, 0xcb, 0x11, 0xd0, 0x93, 0xb8, + 0xa6, 0x83, 0x20, 0xff, 0x9f, 0x77, 0xc3, 0xcc, 0x03, 0x6f, 0x08, 0xbf, 0x40, 0xe7, 0x2b, 0xe2, + 0x79, 0x0c, 0xaa, 0x82, 0x41, 0x3a, 0xea, 0xb9, 0xe4, 0x9a, 0xa4, 0x97, 0x7e, 0xda, 0x7a, 0x17, + 0x66, 0x94, 0xa1, 0x1d, 0x3d, 0xf0, 0xde, 0xb3, 0x0b, 0x72, 0xa7, 0x1c, 0xef, 0xd1, 0x53, 0x3e, + 0x8f, 0x33, 0x26, 0x5f, 0xec, 0x76, 0x2a, 0x49, 0x81, 0x88, 0xee, 0x21, 0xc4, 0x1a, 0xeb, 0xd9, + 0xc5, 0x39, 0x99, 0xcd, 0xad, 0x31, 0x8b, 0x01, 0x18, 0x23, 0xdd, 0x1f, 0x4e, 0x2d, 0xf9, 0x48, + 0x4f, 0xf2, 0x65, 0x8e, 0x78, 0x5c, 0x58, 0x19, 0x8d, 0xe5, 0x98, 0x57, 0x67, 0x7f, 0x05, 0x64, + 0xaf, 0x63, 0xb6, 0xfe, 0xf5, 0xb7, 0x3c, 0xa5, 0xce, 0xe9, 0x68, 0x44, 0xe0, 0x4d, 0x43, 0x69, + 0x29, 0x2e, 0xac, 0x15, 0x59, 0xa8, 0x0a, 0x9e, 0x6e, 0x47, 0xdf, 0x34, 0x35, 0x6a, 0xcf, 0xdc, + 0x22, 0xc9, 0xc0, 0x9b, 0x89, 0xd4, 0xed, 0xab, 0x12, 0xa2, 0x0d, 0x52, 0xbb, 0x02, 0x2f, 0xa9, + 0xd7, 0x61, 0x1e, 0xb4, 0x50, 0x04, 0xf6, 0xc2, 0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xbe, 0x91 +]; + +/** Reed-Solomon matrix for key schedule */ +const RS = [ + [0x01, 0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E], + [0xA4, 0x56, 0x82, 0xF3, 0x1E, 0xC6, 0x68, 0xE5], + [0x02, 0xA1, 0xFC, 0xC1, 0x47, 0xAE, 0x3D, 0x19], + [0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E, 0x03] +]; + +/** + * Galois Field multiplication in GF(2^8) with polynomial 0x169 + */ +function gfMult(a, b, poly) { + let result = 0; + while (b) { + if (b & 1) result ^= a; + a <<= 1; + if (a & 0x100) a ^= poly; + b >>>= 1; + } + return result & 0xFF; +} + +/** + * MDS multiplication + */ +function mdsMultiply(x) { + const b0 = x & 0xFF; + const b1 = (x >>> 8) & 0xFF; + const b2 = (x >>> 16) & 0xFF; + const b3 = (x >>> 24) & 0xFF; + + // MDS matrix multiplication in GF(2^8) with polynomial 0x169 + const r0 = gfMult(b0, 0x01, 0x169) ^ gfMult(b1, 0xEF, 0x169) ^ gfMult(b2, 0x5B, 0x169) ^ gfMult(b3, 0x5B, 0x169); + const r1 = gfMult(b0, 0x5B, 0x169) ^ gfMult(b1, 0xEF, 0x169) ^ gfMult(b2, 0xEF, 0x169) ^ gfMult(b3, 0x01, 0x169); + const r2 = gfMult(b0, 0xEF, 0x169) ^ gfMult(b1, 0x5B, 0x169) ^ gfMult(b2, 0x01, 0x169) ^ gfMult(b3, 0xEF, 0x169); + const r3 = gfMult(b0, 0xEF, 0x169) ^ gfMult(b1, 0x01, 0x169) ^ gfMult(b2, 0xEF, 0x169) ^ gfMult(b3, 0x5B, 0x169); + + return (r3 << 24) | (r2 << 16) | (r1 << 8) | r0; +} + +/** + * Reed-Solomon multiplication for key schedule + */ +function rsMultiply(key8) { + let result = 0; + for (let i = 0; i < 4; i++) { + let x = 0; + for (let j = 0; j < 8; j++) { + x ^= gfMult(RS[i][j], key8[j], 0x14D); + } + result |= x << (i * 8); + } + return result; +} + +/** + * Apply h function (the main keyed permutation) + */ +function h(x, L, k) { + const y = new Array(4); + y[0] = x & 0xFF; + y[1] = (x >>> 8) & 0xFF; + y[2] = (x >>> 16) & 0xFF; + y[3] = (x >>> 24) & 0xFF; + + if (k === 4) { + y[0] = Q1[y[0]] ^ (L[3] & 0xFF); + y[1] = Q0[y[1]] ^ ((L[3] >>> 8) & 0xFF); + y[2] = Q0[y[2]] ^ ((L[3] >>> 16) & 0xFF); + y[3] = Q1[y[3]] ^ ((L[3] >>> 24) & 0xFF); + } + if (k >= 3) { + y[0] = Q1[y[0]] ^ (L[2] & 0xFF); + y[1] = Q1[y[1]] ^ ((L[2] >>> 8) & 0xFF); + y[2] = Q0[y[2]] ^ ((L[2] >>> 16) & 0xFF); + y[3] = Q0[y[3]] ^ ((L[2] >>> 24) & 0xFF); + } + + // Always do k >= 2 + y[0] = Q0[Q0[y[0]] ^ (L[1] & 0xFF)] ^ (L[0] & 0xFF); + y[1] = Q0[Q1[y[1]] ^ ((L[1] >>> 8) & 0xFF)] ^ ((L[0] >>> 8) & 0xFF); + y[2] = Q1[Q0[y[2]] ^ ((L[1] >>> 16) & 0xFF)] ^ ((L[0] >>> 16) & 0xFF); + y[3] = Q1[Q1[y[3]] ^ ((L[1] >>> 24) & 0xFF)] ^ ((L[0] >>> 24) & 0xFF); + + // Final q-box lookup + y[0] = Q1[y[0]]; + y[1] = Q0[y[1]]; + y[2] = Q1[y[2]]; + y[3] = Q0[y[3]]; + + return mdsMultiply((y[3] << 24) | (y[2] << 16) | (y[1] << 8) | y[0]); +} + +/** + * Rotate left 32-bit + */ +function ROL(x, n) { + return ((x << n) | (x >>> (32 - n))) >>> 0; +} + +/** + * Rotate right 32-bit + */ +function ROR(x, n) { + return ((x >>> n) | (x << (32 - n))) >>> 0; +} + +/** + * Generate subkeys from the key + */ +function generateSubkeys(key) { + const keyLen = key.length; + const k = keyLen / 8; // 2, 3, or 4 + + // Split key into Me (even words) and Mo (odd words) + const Me = new Array(k); + const Mo = new Array(k); + + for (let i = 0; i < k; i++) { + const offset = i * 8; + Me[i] = (key[offset]) | (key[offset + 1] << 8) | + (key[offset + 2] << 16) | (key[offset + 3] << 24); + Mo[i] = (key[offset + 4]) | (key[offset + 5] << 8) | + (key[offset + 6] << 16) | (key[offset + 7] << 24); + } + + // Generate S-box keys using Reed-Solomon + const S = new Array(k); + for (let i = 0; i < k; i++) { + const offset = (k - 1 - i) * 8; + S[i] = rsMultiply(key.slice(offset, offset + 8)); + } + + // Generate round subkeys + const subkeys = new Array(40); + const rho = 0x01010101; + + for (let i = 0; i < 20; i++) { + const A = h(2 * i * rho, Me, k); + const B = ROL(h((2 * i + 1) * rho, Mo, k), 8); + subkeys[2 * i] = (A + B) >>> 0; + subkeys[2 * i + 1] = ROL((A + 2 * B) >>> 0, 9); + } + + return { subkeys, S, k }; +} + +/** + * g function using precomputed S-box keys + */ +function g(x, S, k) { + return h(x, S, k); +} + +/** + * Encrypt a single 128-bit block + */ +function encryptBlock(block, keyData) { + const { subkeys, S, k } = keyData; + + // Split block into 4 words (little-endian) + let R0 = (block[0]) | (block[1] << 8) | (block[2] << 16) | (block[3] << 24); + let R1 = (block[4]) | (block[5] << 8) | (block[6] << 16) | (block[7] << 24); + let R2 = (block[8]) | (block[9] << 8) | (block[10] << 16) | (block[11] << 24); + let R3 = (block[12]) | (block[13] << 8) | (block[14] << 16) | (block[15] << 24); + + // Input whitening + R0 ^= subkeys[0]; + R1 ^= subkeys[1]; + R2 ^= subkeys[2]; + R3 ^= subkeys[3]; + + // 16 rounds + for (let r = 0; r < NROUNDS; r += 2) { + let T0 = g(R0, S, k); + let T1 = g(ROL(R1, 8), S, k); + R2 = ROR(R2 ^ ((T0 + T1 + subkeys[8 + 2 * r]) >>> 0), 1); + R3 = ROL(R3, 1) ^ ((T0 + 2 * T1 + subkeys[9 + 2 * r]) >>> 0); + + T0 = g(R2, S, k); + T1 = g(ROL(R3, 8), S, k); + R0 = ROR(R0 ^ ((T0 + T1 + subkeys[8 + 2 * r + 2]) >>> 0), 1); + R1 = ROL(R1, 1) ^ ((T0 + 2 * T1 + subkeys[9 + 2 * r + 2]) >>> 0); + } + + // Output whitening (with undo of last swap) + R2 ^= subkeys[4]; + R3 ^= subkeys[5]; + R0 ^= subkeys[6]; + R1 ^= subkeys[7]; + + // Convert back to bytes (little-endian) + return [ + R2 & 0xFF, (R2 >>> 8) & 0xFF, (R2 >>> 16) & 0xFF, (R2 >>> 24) & 0xFF, + R3 & 0xFF, (R3 >>> 8) & 0xFF, (R3 >>> 16) & 0xFF, (R3 >>> 24) & 0xFF, + R0 & 0xFF, (R0 >>> 8) & 0xFF, (R0 >>> 16) & 0xFF, (R0 >>> 24) & 0xFF, + R1 & 0xFF, (R1 >>> 8) & 0xFF, (R1 >>> 16) & 0xFF, (R1 >>> 24) & 0xFF + ]; +} + +/** + * Decrypt a single 128-bit block + */ +function decryptBlock(block, keyData) { + const { subkeys, S, k } = keyData; + + // Split block into 4 words (little-endian) + let R0 = (block[0]) | (block[1] << 8) | (block[2] << 16) | (block[3] << 24); + let R1 = (block[4]) | (block[5] << 8) | (block[6] << 16) | (block[7] << 24); + let R2 = (block[8]) | (block[9] << 8) | (block[10] << 16) | (block[11] << 24); + let R3 = (block[12]) | (block[13] << 8) | (block[14] << 16) | (block[15] << 24); + + // Input whitening (reverse of output whitening) + R0 ^= subkeys[4]; + R1 ^= subkeys[5]; + R2 ^= subkeys[6]; + R3 ^= subkeys[7]; + + // 16 rounds in reverse + for (let r = NROUNDS - 2; r >= 0; r -= 2) { + let T0 = g(R0, S, k); + let T1 = g(ROL(R1, 8), S, k); + R2 = ROL(R2, 1) ^ ((T0 + T1 + subkeys[8 + 2 * r + 2]) >>> 0); + R3 = ROR(R3 ^ ((T0 + 2 * T1 + subkeys[9 + 2 * r + 2]) >>> 0), 1); + + T0 = g(R2, S, k); + T1 = g(ROL(R3, 8), S, k); + R0 = ROL(R0, 1) ^ ((T0 + T1 + subkeys[8 + 2 * r]) >>> 0); + R1 = ROR(R1 ^ ((T0 + 2 * T1 + subkeys[9 + 2 * r]) >>> 0), 1); + } + + // Output whitening (reverse of input whitening) + R2 ^= subkeys[0]; + R3 ^= subkeys[1]; + R0 ^= subkeys[2]; + R1 ^= subkeys[3]; + + // Convert back to bytes (little-endian) + return [ + R2 & 0xFF, (R2 >>> 8) & 0xFF, (R2 >>> 16) & 0xFF, (R2 >>> 24) & 0xFF, + R3 & 0xFF, (R3 >>> 8) & 0xFF, (R3 >>> 16) & 0xFF, (R3 >>> 24) & 0xFF, + R0 & 0xFF, (R0 >>> 8) & 0xFF, (R0 >>> 16) & 0xFF, (R0 >>> 24) & 0xFF, + R1 & 0xFF, (R1 >>> 8) & 0xFF, (R1 >>> 16) & 0xFF, (R1 >>> 24) & 0xFF + ]; +} + +/** + * XOR two 16-byte blocks + */ +function xorBlocks(a, b) { + const result = new Array(16); + for (let i = 0; i < 16; i++) { + result[i] = a[i] ^ b[i]; + } + return result; +} + +/** + * Increment counter (little-endian) + */ +function incrementCounter(counter) { + const result = [...counter]; + for (let i = 0; i < 16; i++) { + result[i]++; + if (result[i] <= 255) break; + result[i] = 0; + } + return result; +} + +/** + * Apply padding to message + * @param {number[]} message - Original message + * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT") + * @param {number} blockSize - Block size in bytes + * @returns {number[]} - Padded message + */ +function applyPadding(message, padding, blockSize) { + const remainder = message.length % blockSize; + let nPadding = remainder === 0 ? 0 : blockSize - remainder; + + // For PKCS5, always add at least one byte (full block if already aligned) + if (padding === "PKCS5" && remainder === 0) { + nPadding = blockSize; + } + + if (nPadding === 0) return [...message]; + + const paddedMessage = [...message]; + + switch (padding) { + case "NO": + throw new OperationError(`No padding requested but input is not a ${blockSize}-byte multiple.`); + + case "PKCS5": + for (let i = 0; i < nPadding; i++) { + paddedMessage.push(nPadding); + } + break; + + case "ZERO": + for (let i = 0; i < nPadding; i++) { + paddedMessage.push(0); + } + break; + + case "RANDOM": + for (let i = 0; i < nPadding; i++) { + paddedMessage.push(Math.floor(Math.random() * 256)); + } + break; + + case "BIT": + paddedMessage.push(0x80); + for (let i = 1; i < nPadding; i++) { + paddedMessage.push(0); + } + break; + + default: + throw new OperationError(`Unknown padding type: ${padding}`); + } + + return paddedMessage; +} + +/** + * Remove padding from message + * @param {number[]} message - Padded message + * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT") + * @param {number} blockSize - Block size in bytes + * @returns {number[]} - Unpadded message + */ +function removePadding(message, padding, blockSize) { + if (message.length === 0) return message; + + switch (padding) { + case "NO": + case "ZERO": + case "RANDOM": + // These padding types cannot be reliably removed + return message; + + case "PKCS5": { + const padByte = message[message.length - 1]; + if (padByte > 0 && padByte <= blockSize) { + // Verify padding + for (let i = 0; i < padByte; i++) { + if (message[message.length - 1 - i] !== padByte) { + throw new OperationError("Invalid PKCS#5 padding."); + } + } + return message.slice(0, message.length - padByte); + } + throw new OperationError("Invalid PKCS#5 padding."); + } + + case "BIT": { + // Find 0x80 byte working backwards, skipping zeros + for (let i = message.length - 1; i >= 0; i--) { + if (message[i] === 0x80) { + return message.slice(0, i); + } else if (message[i] !== 0) { + throw new OperationError("Invalid BIT padding."); + } + } + throw new OperationError("Invalid BIT padding."); + } + + default: + throw new OperationError(`Unknown padding type: ${padding}`); + } +} + +/** + * Encrypt using Twofish cipher with specified block mode + * + * @param {number[]} message - Plaintext as byte array + * @param {number[]} key - Key (16, 24, or 32 bytes) + * @param {number[]} iv - IV (16 bytes, not used for ECB) + * @param {string} mode - Block cipher mode ("ECB", "CBC", "CFB", "OFB", "CTR") + * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT") + * @returns {number[]} - Ciphertext as byte array + */ +export function encryptTwofish(message, key, iv, mode = "ECB", padding = "PKCS5") { + const messageLength = message.length; + if (messageLength === 0) return []; + + const keyData = generateSubkeys(key); + + // Apply padding for ECB/CBC modes + let paddedMessage; + if (mode === "ECB" || mode === "CBC") { + paddedMessage = applyPadding(message, padding, BLOCKSIZE); + } else { + // Stream modes (CFB, OFB, CTR) don't need padding + paddedMessage = [...message]; + } + + const cipherText = []; + + switch (mode) { + case "ECB": + for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) { + const block = paddedMessage.slice(i, i + BLOCKSIZE); + cipherText.push(...encryptBlock(block, keyData)); + } + break; + + case "CBC": { + let ivBlock = [...iv]; + for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) { + const block = paddedMessage.slice(i, i + BLOCKSIZE); + const xored = xorBlocks(block, ivBlock); + ivBlock = encryptBlock(xored, keyData); + cipherText.push(...ivBlock); + } + break; + } + + case "CFB": { + let ivBlock = [...iv]; + for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) { + const encrypted = encryptBlock(ivBlock, keyData); + const block = paddedMessage.slice(i, i + BLOCKSIZE); + ivBlock = xorBlocks(encrypted, block); + cipherText.push(...ivBlock); + } + return cipherText.slice(0, messageLength); + } + + case "OFB": { + let ivBlock = [...iv]; + for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) { + ivBlock = encryptBlock(ivBlock, keyData); + const block = paddedMessage.slice(i, i + BLOCKSIZE); + cipherText.push(...xorBlocks(ivBlock, block)); + } + return cipherText.slice(0, messageLength); + } + + case "CTR": { + let counter = [...iv]; + for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) { + const encrypted = encryptBlock(counter, keyData); + const block = paddedMessage.slice(i, i + BLOCKSIZE); + cipherText.push(...xorBlocks(encrypted, block)); + counter = incrementCounter(counter); + } + return cipherText.slice(0, messageLength); + } + + default: + throw new OperationError(`Invalid block cipher mode: ${mode}`); + } + + return cipherText; +} + +/** + * Decrypt using Twofish cipher with specified block mode + * + * @param {number[]} cipherText - Ciphertext as byte array + * @param {number[]} key - Key (16, 24, or 32 bytes) + * @param {number[]} iv - IV (16 bytes, not used for ECB) + * @param {string} mode - Block cipher mode ("ECB", "CBC", "CFB", "OFB", "CTR") + * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT") + * @returns {number[]} - Plaintext as byte array + */ +export function decryptTwofish(cipherText, key, iv, mode = "ECB", padding = "PKCS5") { + const originalLength = cipherText.length; + if (originalLength === 0) return []; + + const keyData = generateSubkeys(key); + + if (mode === "ECB" || mode === "CBC") { + if ((originalLength % BLOCKSIZE) !== 0) + throw new OperationError(`Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of 16.`); + } else { + // Pad for stream modes + while ((cipherText.length % BLOCKSIZE) !== 0) + cipherText.push(0); + } + + const plainText = []; + + switch (mode) { + case "ECB": + for (let i = 0; i < cipherText.length; i += BLOCKSIZE) { + const block = cipherText.slice(i, i + BLOCKSIZE); + plainText.push(...decryptBlock(block, keyData)); + } + break; + + case "CBC": { + let ivBlock = [...iv]; + for (let i = 0; i < cipherText.length; i += BLOCKSIZE) { + const block = cipherText.slice(i, i + BLOCKSIZE); + const decrypted = decryptBlock(block, keyData); + plainText.push(...xorBlocks(decrypted, ivBlock)); + ivBlock = block; + } + break; + } + + case "CFB": { + let ivBlock = [...iv]; + for (let i = 0; i < cipherText.length; i += BLOCKSIZE) { + const encrypted = encryptBlock(ivBlock, keyData); + const block = cipherText.slice(i, i + BLOCKSIZE); + plainText.push(...xorBlocks(encrypted, block)); + ivBlock = block; + } + return plainText.slice(0, originalLength); + } + + case "OFB": { + let ivBlock = [...iv]; + for (let i = 0; i < cipherText.length; i += BLOCKSIZE) { + ivBlock = encryptBlock(ivBlock, keyData); + const block = cipherText.slice(i, i + BLOCKSIZE); + plainText.push(...xorBlocks(ivBlock, block)); + } + return plainText.slice(0, originalLength); + } + + case "CTR": { + let counter = [...iv]; + for (let i = 0; i < cipherText.length; i += BLOCKSIZE) { + const encrypted = encryptBlock(counter, keyData); + const block = cipherText.slice(i, i + BLOCKSIZE); + plainText.push(...xorBlocks(encrypted, block)); + counter = incrementCounter(counter); + } + return plainText.slice(0, originalLength); + } + + default: + throw new OperationError(`Invalid block cipher mode: ${mode}`); + } + + // Remove padding for ECB/CBC modes + if (mode === "ECB" || mode === "CBC") { + return removePadding(plainText, padding, BLOCKSIZE); + } + + return plainText.slice(0, originalLength); +} diff --git a/src/core/operations/PRESENTDecrypt.mjs b/src/core/operations/PRESENTDecrypt.mjs new file mode 100644 index 00000000..54d62995 --- /dev/null +++ b/src/core/operations/PRESENTDecrypt.mjs @@ -0,0 +1,94 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { decryptPRESENT } from "../lib/Present.mjs"; + +/** + * PRESENT Decrypt operation + */ +class PRESENTDecrypt extends Operation { + + /** + * PRESENTDecrypt constructor + */ + constructor() { + super(); + + this.name = "PRESENT Decrypt"; + this.module = "Ciphers"; + this.description = "PRESENT is an ultra-lightweight block cipher designed for constrained environments such as RFID tags and sensor networks. It operates on 64-bit blocks and supports 80-bit or 128-bit keys with 31 rounds. Standardised in ISO/IEC 29192-2:2019.

When using CBC mode, the PKCS#7 padding scheme is used."; + this.infoURL = "https://wikipedia.org/wiki/PRESENT_(cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Padding", + "type": "option", + "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType, padding] = args; + + if (key.length !== 10 && key.length !== 16) + throw new OperationError(`Invalid key length: ${key.length} bytes + +PRESENT uses a key length of 10 bytes (80 bits) or 16 bytes (128 bits).`); + + if (iv.length !== 8 && mode !== "ECB") + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +PRESENT uses an IV length of 8 bytes (64 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + input = Utils.convertToByteArray(input, inputType); + const output = decryptPRESENT(input, key, iv, mode, padding); + return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output); + } + +} + +export default PRESENTDecrypt; diff --git a/src/core/operations/PRESENTEncrypt.mjs b/src/core/operations/PRESENTEncrypt.mjs new file mode 100644 index 00000000..2a02c3d3 --- /dev/null +++ b/src/core/operations/PRESENTEncrypt.mjs @@ -0,0 +1,94 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { encryptPRESENT } from "../lib/Present.mjs"; + +/** + * PRESENT Encrypt operation + */ +class PRESENTEncrypt extends Operation { + + /** + * PRESENTEncrypt constructor + */ + constructor() { + super(); + + this.name = "PRESENT Encrypt"; + this.module = "Ciphers"; + this.description = "PRESENT is an ultra-lightweight block cipher designed for constrained environments such as RFID tags and sensor networks. It operates on 64-bit blocks and supports 80-bit or 128-bit keys with 31 rounds. Standardised in ISO/IEC 29192-2:2019.

When using CBC mode, the PKCS#7 padding scheme is used."; + this.infoURL = "https://wikipedia.org/wiki/PRESENT_(cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Padding", + "type": "option", + "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType, padding] = args; + + if (key.length !== 10 && key.length !== 16) + throw new OperationError(`Invalid key length: ${key.length} bytes + +PRESENT uses a key length of 10 bytes (80 bits) or 16 bytes (128 bits).`); + + if (iv.length !== 8 && mode !== "ECB") + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +PRESENT uses an IV length of 8 bytes (64 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + input = Utils.convertToByteArray(input, inputType); + const output = encryptPRESENT(input, key, iv, mode, padding); + return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output); + } + +} + +export default PRESENTEncrypt; diff --git a/src/core/operations/TwofishDecrypt.mjs b/src/core/operations/TwofishDecrypt.mjs new file mode 100644 index 00000000..6d5d033c --- /dev/null +++ b/src/core/operations/TwofishDecrypt.mjs @@ -0,0 +1,94 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { decryptTwofish } from "../lib/Twofish.mjs"; + +/** + * Twofish Decrypt operation + */ +class TwofishDecrypt extends Operation { + + /** + * TwofishDecrypt constructor + */ + constructor() { + super(); + + this.name = "Twofish Decrypt"; + this.module = "Ciphers"; + this.description = "Twofish is a symmetric key block cipher designed by Bruce Schneier. It was one of the five AES finalists. Twofish operates on 128-bit blocks and supports key sizes of 128, 192, or 256 bits with 16 rounds of a Feistel network.

When using CBC or ECB mode, the PKCS#7 padding scheme is used."; + this.infoURL = "https://wikipedia.org/wiki/Twofish"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Padding", + "type": "option", + "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType, padding] = args; + + if (key.length !== 16 && key.length !== 24 && key.length !== 32) + throw new OperationError(`Invalid key length: ${key.length} bytes + +Twofish uses a key length of 16 bytes (128 bits), 24 bytes (192 bits), or 32 bytes (256 bits).`); + + if (iv.length !== 16 && mode !== "ECB") + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +Twofish uses an IV length of 16 bytes (128 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + input = Utils.convertToByteArray(input, inputType); + const output = decryptTwofish(input, key, iv, mode, padding); + return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output); + } + +} + +export default TwofishDecrypt; diff --git a/src/core/operations/TwofishEncrypt.mjs b/src/core/operations/TwofishEncrypt.mjs new file mode 100644 index 00000000..e8e3f16f --- /dev/null +++ b/src/core/operations/TwofishEncrypt.mjs @@ -0,0 +1,94 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { encryptTwofish } from "../lib/Twofish.mjs"; + +/** + * Twofish Encrypt operation + */ +class TwofishEncrypt extends Operation { + + /** + * TwofishEncrypt constructor + */ + constructor() { + super(); + + this.name = "Twofish Encrypt"; + this.module = "Ciphers"; + this.description = "Twofish is a symmetric key block cipher designed by Bruce Schneier. It was one of the five AES finalists. Twofish operates on 128-bit blocks and supports key sizes of 128, 192, or 256 bits with 16 rounds of a Feistel network.

When using CBC or ECB mode, the PKCS#7 padding scheme is used."; + this.infoURL = "https://wikipedia.org/wiki/Twofish"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Padding", + "type": "option", + "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType, padding] = args; + + if (key.length !== 16 && key.length !== 24 && key.length !== 32) + throw new OperationError(`Invalid key length: ${key.length} bytes + +Twofish uses a key length of 16 bytes (128 bits), 24 bytes (192 bits), or 32 bytes (256 bits).`); + + if (iv.length !== 16 && mode !== "ECB") + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +Twofish uses an IV length of 16 bytes (128 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + input = Utils.convertToByteArray(input, inputType); + const output = encryptTwofish(input, key, iv, mode, padding); + return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output); + } + +} + +export default TwofishEncrypt; diff --git a/tests/operations/tests/PRESENT.mjs b/tests/operations/tests/PRESENT.mjs new file mode 100644 index 00000000..f581d22f --- /dev/null +++ b/tests/operations/tests/PRESENT.mjs @@ -0,0 +1,465 @@ +/** + * PRESENT cipher tests. + * + * Test vectors from the original PRESENT paper: + * "PRESENT: An Ultra-Lightweight Block Cipher" + * https://link.springer.com/chapter/10.1007/978-3-540-74735-2_31 + * https://www.iacr.org/archive/ches2007/47270450/47270450.pdf + * + * Note: PKCS5 padding adds an extra block when input is exactly block-aligned. + * Round-trip tests verify correct encryption/decryption behavior. + * + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + // ============================================================ + // OFFICIAL TEST VECTORS from the original PRESENT paper: + // "PRESENT: An Ultra-Lightweight Block Cipher" (Bogdanov et al., CHES 2007) + // https://link.springer.com/chapter/10.1007/978-3-540-74735-2_31 + // Table 3: Test Vectors + // ============================================================ + { + name: "PRESENT Official Vector 1: 80-bit zero key, zero plaintext", + input: "0000000000000000", + expectedOutput: "5579c1387b228445", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + { + name: "PRESENT Official Vector 2: 80-bit all-ones key, zero plaintext", + input: "0000000000000000", + expectedOutput: "e72c46c0f5945049", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "ffffffffffffffffffff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + { + name: "PRESENT Official Vector 3: 80-bit zero key, all-ones plaintext", + input: "ffffffffffffffff", + expectedOutput: "a112ffc72f68417b", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + { + name: "PRESENT Official Vector 4: 80-bit all-ones key, all-ones plaintext", + input: "ffffffffffffffff", + expectedOutput: "3333dcd3213210d2", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "ffffffffffffffffffff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + { + name: "PRESENT Official Vector 5: 128-bit zero key, zero plaintext", + input: "0000000000000000", + expectedOutput: "96db702a2e6900af", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00000000000000000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + { + name: "PRESENT Official Vector 6: 128-bit key (SageMath reference)", + input: "0123456789abcdef", + expectedOutput: "0e9d28685e671dd6", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "0123456789abcdef0123456789abcdef", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + // Decrypt verification of official vectors + { + name: "PRESENT Official Vector 1 Decrypt: 80-bit zero key", + input: "5579c1387b228445", + expectedOutput: "0000000000000000", + recipeConfig: [ + { + op: "PRESENT Decrypt", + args: [ + { string: "00000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + { + name: "PRESENT Official Vector 4 Decrypt: 80-bit all-ones key", + input: "3333dcd3213210d2", + expectedOutput: "ffffffffffffffff", + recipeConfig: [ + { + op: "PRESENT Decrypt", + args: [ + { string: "ffffffffffffffffffff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + { + name: "PRESENT Official Vector 5 Decrypt: 128-bit zero key", + input: "96db702a2e6900af", + expectedOutput: "0000000000000000", + recipeConfig: [ + { + op: "PRESENT Decrypt", + args: [ + { string: "00000000000000000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + { + name: "PRESENT Official Vector 6 Decrypt: 128-bit key (SageMath reference)", + input: "0e9d28685e671dd6", + expectedOutput: "0123456789abcdef", + recipeConfig: [ + { + op: "PRESENT Decrypt", + args: [ + { string: "0123456789abcdef0123456789abcdef", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + // ============================================================ + // Round-trip tests - These verify encryption and decryption work correctly + // ============================================================ + { + name: "PRESENT Round-trip: ECB 80-bit key, short message", + input: "Hello!!!", + expectedOutput: "Hello!!!", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "PRESENT Decrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "PRESENT Round-trip: CBC 80-bit key, long message", + input: "The quick brown fox jumps over the lazy dog", + expectedOutput: "The quick brown fox jumps over the lazy dog", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "aabbccddeeff00112233", option: "Hex" }, + { string: "0011223344556677", option: "Hex" }, + "CBC", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "PRESENT Decrypt", + args: [ + { string: "aabbccddeeff00112233", option: "Hex" }, + { string: "0011223344556677", option: "Hex" }, + "CBC", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "PRESENT Round-trip: ECB 128-bit key", + input: "Testing PRESENT cipher with 128-bit key", + expectedOutput: "Testing PRESENT cipher with 128-bit key", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "PRESENT Decrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "PRESENT Round-trip: CBC 128-bit key", + input: "PRESENT is an ultra-lightweight block cipher!", + expectedOutput: "PRESENT is an ultra-lightweight block cipher!", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + { string: "8877665544332211", option: "Hex" }, + "CBC", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "PRESENT Decrypt", + args: [ + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + { string: "8877665544332211", option: "Hex" }, + "CBC", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "PRESENT Round-trip: UTF8 key (10 bytes)", + input: "Secret message", + expectedOutput: "Secret message", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "mypassword", option: "UTF8" }, + { string: "initvect", option: "UTF8" }, + "CBC", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "PRESENT Decrypt", + args: [ + { string: "mypassword", option: "UTF8" }, + { string: "initvect", option: "UTF8" }, + "CBC", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + + // Encryption consistency tests - verify same input always produces same output + { + name: "PRESENT Encrypt: 80-bit zero key consistency", + input: "TestData", + expectedOutput: "b78cfea5ffcd89f265585a6ce7312131", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + } + ] + }, + { + name: "PRESENT Encrypt: 128-bit zero key consistency", + input: "TestData", + expectedOutput: "e127a24e38de2c36407e794ef5dffefd", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00000000000000000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + } + ] + }, + { + name: "PRESENT Round-trip: Various lengths 1 byte", + input: "A", + expectedOutput: "A", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "PRESENT Decrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "PRESENT Round-trip: Various lengths 7 bytes", + input: "1234567", + expectedOutput: "1234567", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "PRESENT Decrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "PRESENT Round-trip: Various lengths 8 bytes (exact block)", + input: "12345678", + expectedOutput: "12345678", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "PRESENT Decrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "PRESENT Round-trip: Various lengths 9 bytes", + input: "123456789", + expectedOutput: "123456789", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "PRESENT Decrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "PRESENT Round-trip: Various lengths 16 bytes (two blocks)", + input: "1234567890ABCDEF", + expectedOutput: "1234567890ABCDEF", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "PRESENT Decrypt", + args: [ + { string: "00112233445566778899", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "PRESENT Round-trip: Binary data", + input: "\x00\x01\x02\x03\x04\x05\x06\x07", + expectedOutput: "\x00\x01\x02\x03\x04\x05\x06\x07", + recipeConfig: [ + { + op: "PRESENT Encrypt", + args: [ + { string: "ffeeddccbbaa99887766", option: "Hex" }, + { string: "0011223344556677", option: "Hex" }, + "CBC", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "PRESENT Decrypt", + args: [ + { string: "ffeeddccbbaa99887766", option: "Hex" }, + { string: "0011223344556677", option: "Hex" }, + "CBC", "Hex", "Raw", "PKCS5" + ] + } + ] + } +]); diff --git a/tests/operations/tests/Twofish.mjs b/tests/operations/tests/Twofish.mjs new file mode 100644 index 00000000..d92fd0ea --- /dev/null +++ b/tests/operations/tests/Twofish.mjs @@ -0,0 +1,486 @@ +/** + * Twofish cipher tests. + * + * Test vectors from the official Twofish paper: + * https://www.schneier.com/academic/twofish/ + * + * Note: PKCS5 padding adds an extra block when input is exactly block-aligned. + * Round-trip tests verify correct encryption/decryption behavior. + * + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + // ============================================================ + // OFFICIAL TEST VECTORS from Bruce Schneier's Twofish paper: + // https://www.schneier.com/academic/twofish/ + // https://www.schneier.com/wp-content/uploads/2015/12/ecb_ival.txt + // ============================================================ + { + name: "Twofish Official Vector: 128-bit zero key, zero plaintext", + input: "00000000000000000000000000000000", + expectedOutput: "9f589f5cf6122c32b6bfec2f2ae8c35a", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "00000000000000000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + { + name: "Twofish Official Vector: 192-bit zero key, zero plaintext", + input: "00000000000000000000000000000000", + expectedOutput: "efa71f788965bd4453f860178fc19101", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "000000000000000000000000000000000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + { + name: "Twofish Official Vector: 256-bit zero key, zero plaintext", + input: "00000000000000000000000000000000", + expectedOutput: "57ff739d4dc92c1bd7fc01700cc8216f", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "0000000000000000000000000000000000000000000000000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + // Decrypt verification of official vectors + { + name: "Twofish Official Vector Decrypt: 128-bit zero key", + input: "9f589f5cf6122c32b6bfec2f2ae8c35a", + expectedOutput: "00000000000000000000000000000000", + recipeConfig: [ + { + op: "Twofish Decrypt", + args: [ + { string: "00000000000000000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Hex", "NO" + ] + } + ] + }, + // ============================================================ + // Round-trip tests for ECB mode with various key sizes + // ============================================================ + { + name: "Twofish Round-trip: ECB 128-bit key", + input: "Hello, World!!!", + expectedOutput: "Hello, World!!!", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "Twofish Round-trip: ECB 192-bit key", + input: "Testing Twofish with 192-bit key", + expectedOutput: "Testing Twofish with 192-bit key", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "Twofish Round-trip: ECB 256-bit key", + input: "Testing Twofish with 256-bit key encryption", + expectedOutput: "Testing Twofish with 256-bit key encryption", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + + // Round-trip tests for CBC mode + { + name: "Twofish Round-trip: CBC 128-bit key", + input: "The quick brown fox jumps over the lazy dog", + expectedOutput: "The quick brown fox jumps over the lazy dog", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + "CBC", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + "CBC", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "Twofish Round-trip: CBC 192-bit key", + input: "Testing Twofish with 192-bit key in CBC mode", + expectedOutput: "Testing Twofish with 192-bit key in CBC mode", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" }, + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + "CBC", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" }, + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + "CBC", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "Twofish Round-trip: CBC 256-bit key", + input: "Testing Twofish with 256-bit key in CBC mode", + expectedOutput: "Testing Twofish with 256-bit key in CBC mode", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + "CBC", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" }, + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + "CBC", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + + // Round-trip tests for CFB mode + { + name: "Twofish Round-trip: CFB 128-bit key", + input: "Testing Twofish CFB mode encryption", + expectedOutput: "Testing Twofish CFB mode encryption", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "deadbeefcafebabe0123456789abcdef", option: "Hex" }, + { string: "0102030405060708090a0b0c0d0e0f10", option: "Hex" }, + "CFB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "deadbeefcafebabe0123456789abcdef", option: "Hex" }, + { string: "0102030405060708090a0b0c0d0e0f10", option: "Hex" }, + "CFB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + + // Round-trip tests for OFB mode + { + name: "Twofish Round-trip: OFB 128-bit key", + input: "Testing Twofish OFB mode encryption", + expectedOutput: "Testing Twofish OFB mode encryption", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + "OFB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + "OFB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + + // Round-trip tests for CTR mode + { + name: "Twofish Round-trip: CTR 128-bit key", + input: "Testing Twofish CTR mode encryption", + expectedOutput: "Testing Twofish CTR mode encryption", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "00000000000000000000000000000001", option: "Hex" }, + "CTR", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "00000000000000000000000000000001", option: "Hex" }, + "CTR", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + + // UTF8 key tests + { + name: "Twofish Round-trip: UTF8 key (16 bytes)", + input: "Secret message!", + expectedOutput: "Secret message!", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "MySecretPassword", option: "UTF8" }, + { string: "InitVectorHere!!", option: "UTF8" }, + "CBC", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "MySecretPassword", option: "UTF8" }, + { string: "InitVectorHere!!", option: "UTF8" }, + "CBC", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + + // Various input length tests + { + name: "Twofish Round-trip: 1 byte input", + input: "A", + expectedOutput: "A", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "Twofish Round-trip: 15 byte input", + input: "123456789012345", + expectedOutput: "123456789012345", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "Twofish Round-trip: 16 byte input (exact block)", + input: "1234567890123456", + expectedOutput: "1234567890123456", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "Twofish Round-trip: 17 byte input", + input: "12345678901234567", + expectedOutput: "12345678901234567", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + { + name: "Twofish Round-trip: 32 byte input (two blocks)", + input: "12345678901234567890123456789012", + expectedOutput: "12345678901234567890123456789012", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + + // Binary data test + { + name: "Twofish Round-trip: Binary data", + input: "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + expectedOutput: "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + "CBC", "Raw", "Hex", "PKCS5" + ] + }, + { + op: "Twofish Decrypt", + args: [ + { string: "ffeeddccbbaa99887766554433221100", option: "Hex" }, + { string: "00112233445566778899aabbccddeeff", option: "Hex" }, + "CBC", "Hex", "Raw", "PKCS5" + ] + } + ] + }, + + // Consistency test - same input should always produce same output + { + name: "Twofish Encrypt: 128-bit key consistency test", + input: "TestData12345678", + expectedOutput: "8aed2d3a85dc3e0b663ba1fe1fdaf056771d591428af301d69fa1e227d083527", + recipeConfig: [ + { + op: "Twofish Encrypt", + args: [ + { string: "00000000000000000000000000000000", option: "Hex" }, + { string: "", option: "Hex" }, + "ECB", "Raw", "Hex", "PKCS5" + ] + } + ] + } +]); From 002ed911b1a39f303b70246ac0ebf455a91c6d0f Mon Sep 17 00:00:00 2001 From: Thomas M <44269971+thomasxm@users.noreply.github.com> Date: Sun, 5 Jul 2026 13:22:27 +0100 Subject: [PATCH 02/25] feat: add TEA and XTEA block ciphers (#2225) --- src/core/config/Categories.json | 4 + src/core/lib/TEA.mjs | 494 ++++++++++++++++++++++++ src/core/operations/TEADecrypt.mjs | 98 +++++ src/core/operations/TEAEncrypt.mjs | 98 +++++ src/core/operations/XTEADecrypt.mjs | 110 ++++++ src/core/operations/XTEAEncrypt.mjs | 110 ++++++ tests/operations/tests/TEA.mjs | 566 ++++++++++++++++++++++++++++ 7 files changed, 1480 insertions(+) create mode 100644 src/core/lib/TEA.mjs create mode 100644 src/core/operations/TEADecrypt.mjs create mode 100644 src/core/operations/TEAEncrypt.mjs create mode 100644 src/core/operations/XTEADecrypt.mjs create mode 100644 src/core/operations/XTEAEncrypt.mjs create mode 100644 tests/operations/tests/TEA.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 8c64c398..c652ed48 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -135,6 +135,10 @@ "XOR Brute Force", "Vigenère Encode", "Vigenère Decode", + "TEA Encrypt", + "TEA Decrypt", + "XTEA Encrypt", + "XTEA Decrypt", "XXTEA Encrypt", "XXTEA Decrypt", "To Morse Code", diff --git a/src/core/lib/TEA.mjs b/src/core/lib/TEA.mjs new file mode 100644 index 00000000..d2027c2d --- /dev/null +++ b/src/core/lib/TEA.mjs @@ -0,0 +1,494 @@ +/** + * TEA and XTEA block cipher implementation. + * + * TEA (Tiny Encryption Algorithm) — Wheeler & Needham, 1994. + * XTEA (Extended TEA) — Wheeler & Needham, 1997. + * + * Both operate on 64-bit blocks with 128-bit keys. + * TEA uses 32 cycles (64 Feistel rounds). + * XTEA uses 32 cycles (64 Feistel rounds) with improved key schedule. + * + * References: + * https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm + * https://en.wikipedia.org/wiki/XTEA + * https://www.cix.co.uk/~klockstone/teavect.htm + * + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; + +/** TEA/XTEA constants */ +const DELTA = 0x9E3779B9; +const BLOCK_SIZE = 8; // 64-bit block = 8 bytes +const ROUNDS = 32; // 32 cycles + +/** + * Convert byte array to array of 32-bit unsigned integers (big-endian) + * @param {number[]} bytes + * @returns {number[]} + */ +function bytesToUint32(bytes) { + const words = []; + for (let i = 0; i < bytes.length; i += 4) { + words.push( + ((bytes[i] << 24) | (bytes[i + 1] << 16) | + (bytes[i + 2] << 8) | bytes[i + 3]) >>> 0 + ); + } + return words; +} + +/** + * Convert array of 32-bit unsigned integers to byte array (big-endian) + * @param {number[]} words + * @returns {number[]} + */ +function uint32ToBytes(words) { + const bytes = []; + for (const w of words) { + bytes.push((w >>> 24) & 0xFF); + bytes.push((w >>> 16) & 0xFF); + bytes.push((w >>> 8) & 0xFF); + bytes.push(w & 0xFF); + } + return bytes; +} + +/** + * TEA encrypt a single 64-bit block + * Reference: Wheeler & Needham, 1994 + * + * @param {number[]} block - 8 bytes (plaintext) + * @param {number[]} key - 16 bytes (128-bit key) + * @returns {number[]} - 8 bytes (ciphertext) + */ +function teaEncryptBlock(block, key) { + const v = bytesToUint32(block); + const k = bytesToUint32(key); + let v0 = v[0], v1 = v[1]; + let sum = 0; + + for (let i = 0; i < ROUNDS; i++) { + sum = (sum + DELTA) >>> 0; + v0 = (v0 + ((((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >>> 5) + k[1])))) >>> 0; + v1 = (v1 + ((((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >>> 5) + k[3])))) >>> 0; + } + + return uint32ToBytes([v0, v1]); +} + +/** + * TEA decrypt a single 64-bit block + * + * @param {number[]} block - 8 bytes (ciphertext) + * @param {number[]} key - 16 bytes (128-bit key) + * @returns {number[]} - 8 bytes (plaintext) + */ +function teaDecryptBlock(block, key) { + const v = bytesToUint32(block); + const k = bytesToUint32(key); + let v0 = v[0], v1 = v[1]; + let sum = (DELTA * ROUNDS) >>> 0; + + for (let i = 0; i < ROUNDS; i++) { + v1 = (v1 - ((((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >>> 5) + k[3])))) >>> 0; + v0 = (v0 - ((((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >>> 5) + k[1])))) >>> 0; + sum = (sum - DELTA) >>> 0; + } + + return uint32ToBytes([v0, v1]); +} + +/** + * XTEA encrypt a single 64-bit block + * Reference: Wheeler & Needham, 1997 + * + * @param {number[]} block - 8 bytes (plaintext) + * @param {number[]} key - 16 bytes (128-bit key) + * @param {number} rounds - Number of rounds (default 32) + * @returns {number[]} - 8 bytes (ciphertext) + */ +function xteaEncryptBlock(block, key, rounds) { + const v = bytesToUint32(block); + const k = bytesToUint32(key); + let v0 = v[0], v1 = v[1]; + let sum = 0; + + for (let i = 0; i < rounds; i++) { + v0 = (v0 + ((((v1 << 4) ^ (v1 >>> 5)) + v1) ^ (sum + k[sum & 3]))) >>> 0; + sum = (sum + DELTA) >>> 0; + v1 = (v1 + ((((v0 << 4) ^ (v0 >>> 5)) + v0) ^ (sum + k[(sum >>> 11) & 3]))) >>> 0; + } + + return uint32ToBytes([v0, v1]); +} + +/** + * XTEA decrypt a single 64-bit block + * + * @param {number[]} block - 8 bytes (ciphertext) + * @param {number[]} key - 16 bytes (128-bit key) + * @param {number} rounds - Number of rounds (default 32) + * @returns {number[]} - 8 bytes (plaintext) + */ +function xteaDecryptBlock(block, key, rounds) { + const v = bytesToUint32(block); + const k = bytesToUint32(key); + let v0 = v[0], v1 = v[1]; + let sum = (DELTA * rounds) >>> 0; + + for (let i = 0; i < rounds; i++) { + v1 = (v1 - ((((v0 << 4) ^ (v0 >>> 5)) + v0) ^ (sum + k[(sum >>> 11) & 3]))) >>> 0; + sum = (sum - DELTA) >>> 0; + v0 = (v0 - ((((v1 << 4) ^ (v1 >>> 5)) + v1) ^ (sum + k[sum & 3]))) >>> 0; + } + + return uint32ToBytes([v0, v1]); +} + +/** + * XOR two byte arrays of equal length + * @param {number[]} a + * @param {number[]} b + * @returns {number[]} + */ +function xorBlocks(a, b) { + return a.map((byte, i) => byte ^ b[i]); +} + +/** + * Increment a byte array as a big-endian counter + * @param {number[]} counter + * @returns {number[]} + */ +function incrementCounter(counter) { + const result = [...counter]; + for (let i = result.length - 1; i >= 0; i--) { + result[i] = (result[i] + 1) & 0xFF; + if (result[i] !== 0) break; + } + return result; +} + +/** + * Apply padding to message + * @param {number[]} message + * @param {string} padding - "NO", "PKCS5", "ZERO", "RANDOM", "BIT" + * @returns {number[]} + */ +function applyPadding(message, padding) { + const remainder = message.length % BLOCK_SIZE; + if (remainder === 0 && padding !== "PKCS5") return [...message]; + + const nPadding = (remainder === 0 && padding === "PKCS5") ? + BLOCK_SIZE : + BLOCK_SIZE - remainder; + + if (nPadding === 0) return [...message]; + + const padded = [...message]; + + switch (padding) { + case "NO": + throw new OperationError( + `No padding requested but input length (${message.length} bytes) is not a multiple of ${BLOCK_SIZE} bytes.` + ); + case "PKCS5": + for (let i = 0; i < nPadding; i++) padded.push(nPadding); + break; + case "ZERO": + for (let i = 0; i < nPadding; i++) padded.push(0); + break; + case "RANDOM": + for (let i = 0; i < nPadding; i++) padded.push(Math.floor(Math.random() * 256)); + break; + case "BIT": + padded.push(0x80); + for (let i = 1; i < nPadding; i++) padded.push(0); + break; + default: + throw new OperationError(`Unknown padding type: ${padding}`); + } + + return padded; +} + +/** + * Remove padding from message + * @param {number[]} message + * @param {string} padding + * @returns {number[]} + */ +function removePadding(message, padding) { + if (message.length === 0) return message; + + switch (padding) { + case "NO": + case "ZERO": + case "RANDOM": + return message; + + case "PKCS5": { + const padByte = message[message.length - 1]; + if (padByte > 0 && padByte <= BLOCK_SIZE) { + for (let i = 0; i < padByte; i++) { + if (message[message.length - 1 - i] !== padByte) { + throw new OperationError("Invalid PKCS#5 padding."); + } + } + return message.slice(0, message.length - padByte); + } + throw new OperationError("Invalid PKCS#5 padding."); + } + + case "BIT": { + for (let i = message.length - 1; i >= 0; i--) { + if (message[i] === 0x80) return message.slice(0, i); + if (message[i] !== 0) throw new OperationError("Invalid BIT padding."); + } + throw new OperationError("Invalid BIT padding."); + } + + default: + throw new OperationError(`Unknown padding type: ${padding}`); + } +} + +/** + * Encrypt with block cipher modes + * + * @param {number[]} message - Plaintext bytes + * @param {number[]} key - 16-byte key + * @param {number[]} iv - 8-byte IV (ignored for ECB) + * @param {string} mode - "ECB", "CBC", "CFB", "OFB", "CTR" + * @param {string} padding - "PKCS5", "NO", "ZERO", "RANDOM", "BIT" + * @param {Function} encryptBlockFn - Block encrypt function + * @returns {number[]} - Ciphertext bytes + */ +function encryptWithMode(message, key, iv, mode, padding, encryptBlockFn) { + const messageLength = message.length; + if (messageLength === 0) return []; + + let data; + if (mode === "ECB" || mode === "CBC") { + data = applyPadding(message, padding); + } else { + data = [...message]; + } + + const cipherText = []; + + switch (mode) { + case "ECB": + for (let i = 0; i < data.length; i += BLOCK_SIZE) { + cipherText.push(...encryptBlockFn(data.slice(i, i + BLOCK_SIZE), key)); + } + break; + + case "CBC": { + let ivBlock = [...iv]; + for (let i = 0; i < data.length; i += BLOCK_SIZE) { + const block = data.slice(i, i + BLOCK_SIZE); + const xored = xorBlocks(block, ivBlock); + ivBlock = encryptBlockFn(xored, key); + cipherText.push(...ivBlock); + } + break; + } + + case "CFB": { + let ivBlock = [...iv]; + for (let i = 0; i < data.length; i += BLOCK_SIZE) { + const encrypted = encryptBlockFn(ivBlock, key); + const block = data.slice(i, i + BLOCK_SIZE); + while (block.length < BLOCK_SIZE) block.push(0); + ivBlock = xorBlocks(encrypted, block); + cipherText.push(...ivBlock); + } + return cipherText.slice(0, messageLength); + } + + case "OFB": { + let ivBlock = [...iv]; + for (let i = 0; i < data.length; i += BLOCK_SIZE) { + ivBlock = encryptBlockFn(ivBlock, key); + const block = data.slice(i, i + BLOCK_SIZE); + while (block.length < BLOCK_SIZE) block.push(0); + cipherText.push(...xorBlocks(ivBlock, block)); + } + return cipherText.slice(0, messageLength); + } + + case "CTR": { + let counter = [...iv]; + for (let i = 0; i < data.length; i += BLOCK_SIZE) { + const encrypted = encryptBlockFn(counter, key); + const block = data.slice(i, i + BLOCK_SIZE); + while (block.length < BLOCK_SIZE) block.push(0); + cipherText.push(...xorBlocks(encrypted, block)); + counter = incrementCounter(counter); + } + return cipherText.slice(0, messageLength); + } + + default: + throw new OperationError(`Invalid block cipher mode: ${mode}`); + } + + return cipherText; +} + +/** + * Decrypt with block cipher modes + * + * @param {number[]} cipherText - Ciphertext bytes + * @param {number[]} key - 16-byte key + * @param {number[]} iv - 8-byte IV (ignored for ECB) + * @param {string} mode - "ECB", "CBC", "CFB", "OFB", "CTR" + * @param {string} padding - "PKCS5", "NO", "ZERO", "RANDOM", "BIT" + * @param {Function} encryptBlockFn - Block encrypt function (used for stream modes) + * @param {Function} decryptBlockFn - Block decrypt function (used for ECB/CBC) + * @returns {number[]} - Plaintext bytes + */ +function decryptWithMode(cipherText, key, iv, mode, padding, encryptBlockFn, decryptBlockFn) { + const originalLength = cipherText.length; + if (originalLength === 0) return []; + + if (mode === "ECB" || mode === "CBC") { + if ((originalLength % BLOCK_SIZE) !== 0) + throw new OperationError( + `Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of ${BLOCK_SIZE}.` + ); + } else { + while ((cipherText.length % BLOCK_SIZE) !== 0) + cipherText.push(0); + } + + const plainText = []; + + switch (mode) { + case "ECB": + for (let i = 0; i < cipherText.length; i += BLOCK_SIZE) { + plainText.push(...decryptBlockFn(cipherText.slice(i, i + BLOCK_SIZE), key)); + } + break; + + case "CBC": { + let ivBlock = [...iv]; + for (let i = 0; i < cipherText.length; i += BLOCK_SIZE) { + const block = cipherText.slice(i, i + BLOCK_SIZE); + const decrypted = decryptBlockFn(block, key); + plainText.push(...xorBlocks(decrypted, ivBlock)); + ivBlock = block; + } + break; + } + + case "CFB": { + let ivBlock = [...iv]; + for (let i = 0; i < cipherText.length; i += BLOCK_SIZE) { + const encrypted = encryptBlockFn(ivBlock, key); + const block = cipherText.slice(i, i + BLOCK_SIZE); + plainText.push(...xorBlocks(encrypted, block)); + ivBlock = block; + } + return plainText.slice(0, originalLength); + } + + case "OFB": { + let ivBlock = [...iv]; + for (let i = 0; i < cipherText.length; i += BLOCK_SIZE) { + ivBlock = encryptBlockFn(ivBlock, key); + const block = cipherText.slice(i, i + BLOCK_SIZE); + plainText.push(...xorBlocks(ivBlock, block)); + } + return plainText.slice(0, originalLength); + } + + case "CTR": { + let counter = [...iv]; + for (let i = 0; i < cipherText.length; i += BLOCK_SIZE) { + const encrypted = encryptBlockFn(counter, key); + const block = cipherText.slice(i, i + BLOCK_SIZE); + plainText.push(...xorBlocks(encrypted, block)); + counter = incrementCounter(counter); + } + return plainText.slice(0, originalLength); + } + + default: + throw new OperationError(`Invalid block cipher mode: ${mode}`); + } + + if (mode === "ECB" || mode === "CBC") { + return removePadding(plainText, padding); + } + + return plainText.slice(0, originalLength); +} + + +// ==================== PUBLIC API ==================== + +/** + * Encrypt using TEA cipher + * @param {number[]} message - Plaintext bytes + * @param {number[]} key - 16-byte key + * @param {number[]} iv - 8-byte IV + * @param {string} mode - Block cipher mode + * @param {string} padding - Padding type + * @returns {number[]} - Ciphertext bytes + */ +export function encryptTEA(message, key, iv, mode = "ECB", padding = "PKCS5") { + return encryptWithMode(message, key, iv, mode, padding, teaEncryptBlock); +} + +/** + * Decrypt using TEA cipher + * @param {number[]} cipherText - Ciphertext bytes + * @param {number[]} key - 16-byte key + * @param {number[]} iv - 8-byte IV + * @param {string} mode - Block cipher mode + * @param {string} padding - Padding type + * @returns {number[]} - Plaintext bytes + */ +export function decryptTEA(cipherText, key, iv, mode = "ECB", padding = "PKCS5") { + return decryptWithMode(cipherText, key, iv, mode, padding, teaEncryptBlock, teaDecryptBlock); +} + +/** + * Encrypt using XTEA cipher + * @param {number[]} message - Plaintext bytes + * @param {number[]} key - 16-byte key + * @param {number[]} iv - 8-byte IV + * @param {string} mode - Block cipher mode + * @param {string} padding - Padding type + * @param {number} rounds - Number of rounds (default 32) + * @returns {number[]} - Ciphertext bytes + */ +export function encryptXTEA(message, key, iv, mode = "ECB", padding = "PKCS5", rounds = 32) { + const encFn = (block, k) => xteaEncryptBlock(block, k, rounds); + return encryptWithMode(message, key, iv, mode, padding, encFn); +} + +/** + * Decrypt using XTEA cipher + * @param {number[]} cipherText - Ciphertext bytes + * @param {number[]} key - 16-byte key + * @param {number[]} iv - 8-byte IV + * @param {string} mode - Block cipher mode + * @param {string} padding - Padding type + * @param {number} rounds - Number of rounds (default 32) + * @returns {number[]} - Plaintext bytes + */ +export function decryptXTEA(cipherText, key, iv, mode = "ECB", padding = "PKCS5", rounds = 32) { + const encFn = (block, k) => xteaEncryptBlock(block, k, rounds); + const decFn = (block, k) => xteaDecryptBlock(block, k, rounds); + return decryptWithMode(cipherText, key, iv, mode, padding, encFn, decFn); +} + +/** Block size in bytes (exported for operation validation) */ +export const TEA_BLOCK_SIZE = BLOCK_SIZE; diff --git a/src/core/operations/TEADecrypt.mjs b/src/core/operations/TEADecrypt.mjs new file mode 100644 index 00000000..ab0a634b --- /dev/null +++ b/src/core/operations/TEADecrypt.mjs @@ -0,0 +1,98 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { decryptTEA, TEA_BLOCK_SIZE } from "../lib/TEA.mjs"; + +/** + * TEA Decrypt operation + */ +class TEADecrypt extends Operation { + + /** + * TEADecrypt constructor + */ + constructor() { + super(); + + this.name = "TEA Decrypt"; + this.module = "Ciphers"; + this.description = "TEA (Tiny Encryption Algorithm) is a block cipher designed by David Wheeler and Roger Needham in 1994. It operates on 64-bit blocks using a 128-bit key and performs 32 cycles (64 Feistel rounds) with the DELTA constant 0x9E3779B9 derived from the golden ratio.

TEA is notable for its simplicity and compact implementation, making it frequently encountered in malware analysis and CTF challenges. Despite its elegance, TEA has known weaknesses including equivalent keys and susceptibility to related-key attacks, leading to successors XTEA and XXTEA.

Key: Must be exactly 16 bytes (128 bits).

IV: The Initialisation Vector should be 8 bytes (64 bits). If not entered, it will default to null bytes.

Padding: In CBC and ECB mode, the PKCS#5 padding scheme is used."; + this.infoURL = "https://wikipedia.org/wiki/Tiny_Encryption_Algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Padding", + "type": "option", + "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType, padding] = args; + + if (key.length !== 16) + throw new OperationError(`Invalid key length: ${key.length} bytes + +TEA requires a key length of 16 bytes (128 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + if (iv.length !== TEA_BLOCK_SIZE && iv.length !== 0 && mode !== "ECB") + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +TEA uses an IV length of ${TEA_BLOCK_SIZE} bytes (${TEA_BLOCK_SIZE * 8} bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + // Default IV to null bytes if empty (like AES) + const actualIv = iv.length === 0 ? new Array(TEA_BLOCK_SIZE).fill(0) : iv; + + input = Utils.convertToByteArray(input, inputType); + const output = decryptTEA(input, key, actualIv, mode, padding); + return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output); + } + +} + +export default TEADecrypt; diff --git a/src/core/operations/TEAEncrypt.mjs b/src/core/operations/TEAEncrypt.mjs new file mode 100644 index 00000000..c3f175dd --- /dev/null +++ b/src/core/operations/TEAEncrypt.mjs @@ -0,0 +1,98 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { encryptTEA, TEA_BLOCK_SIZE } from "../lib/TEA.mjs"; + +/** + * TEA Encrypt operation + */ +class TEAEncrypt extends Operation { + + /** + * TEAEncrypt constructor + */ + constructor() { + super(); + + this.name = "TEA Encrypt"; + this.module = "Ciphers"; + this.description = "TEA (Tiny Encryption Algorithm) is a block cipher designed by David Wheeler and Roger Needham in 1994. It operates on 64-bit blocks using a 128-bit key and performs 32 cycles (64 Feistel rounds) with the DELTA constant 0x9E3779B9 derived from the golden ratio.

TEA is notable for its simplicity and compact implementation, making it frequently encountered in malware analysis and CTF challenges. Despite its elegance, TEA has known weaknesses including equivalent keys and susceptibility to related-key attacks, leading to successors XTEA and XXTEA.

Key: Must be exactly 16 bytes (128 bits).

IV: The Initialisation Vector should be 8 bytes (64 bits). If not entered, it will default to null bytes.

Padding: In CBC and ECB mode, the PKCS#5 padding scheme is used."; + this.infoURL = "https://wikipedia.org/wiki/Tiny_Encryption_Algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Padding", + "type": "option", + "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType, padding] = args; + + if (key.length !== 16) + throw new OperationError(`Invalid key length: ${key.length} bytes + +TEA requires a key length of 16 bytes (128 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + if (iv.length !== TEA_BLOCK_SIZE && iv.length !== 0 && mode !== "ECB") + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +TEA uses an IV length of ${TEA_BLOCK_SIZE} bytes (${TEA_BLOCK_SIZE * 8} bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + // Default IV to null bytes if empty (like AES) + const actualIv = iv.length === 0 ? new Array(TEA_BLOCK_SIZE).fill(0) : iv; + + input = Utils.convertToByteArray(input, inputType); + const output = encryptTEA(input, key, actualIv, mode, padding); + return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output); + } + +} + +export default TEAEncrypt; diff --git a/src/core/operations/XTEADecrypt.mjs b/src/core/operations/XTEADecrypt.mjs new file mode 100644 index 00000000..e3b6560b --- /dev/null +++ b/src/core/operations/XTEADecrypt.mjs @@ -0,0 +1,110 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { decryptXTEA, TEA_BLOCK_SIZE } from "../lib/TEA.mjs"; + +/** + * XTEA Decrypt operation + */ +class XTEADecrypt extends Operation { + + /** + * XTEADecrypt constructor + */ + constructor() { + super(); + + this.name = "XTEA Decrypt"; + this.module = "Ciphers"; + this.description = "XTEA (eXtended Tiny Encryption Algorithm) is a block cipher designed by David Wheeler and Roger Needham in 1997 as a successor to TEA, correcting several weaknesses identified in the original algorithm. It operates on 64-bit blocks using a 128-bit key with an improved key schedule that uses sum-dependent key word selection to resist related-key attacks.

XTEA retains the simplicity and compact implementation of TEA whilst providing significantly improved security. It is frequently encountered in malware analysis and CTF challenges due to its straightforward implementation.

Key: Must be exactly 16 bytes (128 bits).

IV: The Initialisation Vector should be 8 bytes (64 bits). If not entered, it will default to null bytes.

Rounds: The recommended number of rounds is 32 (default). The reference implementation by Wheeler & Needham accepts a configurable round count.

Padding: In CBC and ECB mode, the PKCS#5 padding scheme is used."; + this.infoURL = "https://wikipedia.org/wiki/XTEA"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Padding", + "type": "option", + "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"] + }, + { + "name": "Rounds", + "type": "number", + "value": 32, + "min": 1, + "max": 255 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType, padding, rounds] = args; + + if (key.length !== 16) + throw new OperationError(`Invalid key length: ${key.length} bytes + +XTEA requires a key length of 16 bytes (128 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + if (iv.length !== TEA_BLOCK_SIZE && iv.length !== 0 && mode !== "ECB") + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +XTEA uses an IV length of ${TEA_BLOCK_SIZE} bytes (${TEA_BLOCK_SIZE * 8} bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255) + throw new OperationError(`Invalid number of rounds: ${rounds} + +Rounds must be an integer between 1 and 255. Standard XTEA uses 32 rounds.`); + + // Default IV to null bytes if empty (like AES) + const actualIv = iv.length === 0 ? new Array(TEA_BLOCK_SIZE).fill(0) : iv; + + input = Utils.convertToByteArray(input, inputType); + const output = decryptXTEA(input, key, actualIv, mode, padding, rounds); + return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output); + } + +} + +export default XTEADecrypt; diff --git a/src/core/operations/XTEAEncrypt.mjs b/src/core/operations/XTEAEncrypt.mjs new file mode 100644 index 00000000..7d4bc1c1 --- /dev/null +++ b/src/core/operations/XTEAEncrypt.mjs @@ -0,0 +1,110 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { encryptXTEA, TEA_BLOCK_SIZE } from "../lib/TEA.mjs"; + +/** + * XTEA Encrypt operation + */ +class XTEAEncrypt extends Operation { + + /** + * XTEAEncrypt constructor + */ + constructor() { + super(); + + this.name = "XTEA Encrypt"; + this.module = "Ciphers"; + this.description = "XTEA (eXtended Tiny Encryption Algorithm) is a block cipher designed by David Wheeler and Roger Needham in 1997 as a successor to TEA, correcting several weaknesses identified in the original algorithm. It operates on 64-bit blocks using a 128-bit key with an improved key schedule that uses sum-dependent key word selection to resist related-key attacks.

XTEA retains the simplicity and compact implementation of TEA whilst providing significantly improved security. It is frequently encountered in malware analysis and CTF challenges due to its straightforward implementation.

Key: Must be exactly 16 bytes (128 bits).

IV: The Initialisation Vector should be 8 bytes (64 bits). If not entered, it will default to null bytes.

Rounds: The recommended number of rounds is 32 (default). The reference implementation by Wheeler & Needham accepts a configurable round count.

Padding: In CBC and ECB mode, the PKCS#5 padding scheme is used."; + this.infoURL = "https://wikipedia.org/wiki/XTEA"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Padding", + "type": "option", + "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"] + }, + { + "name": "Rounds", + "type": "number", + "value": 32, + "min": 1, + "max": 255 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType, padding, rounds] = args; + + if (key.length !== 16) + throw new OperationError(`Invalid key length: ${key.length} bytes + +XTEA requires a key length of 16 bytes (128 bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + if (iv.length !== TEA_BLOCK_SIZE && iv.length !== 0 && mode !== "ECB") + throw new OperationError(`Invalid IV length: ${iv.length} bytes + +XTEA uses an IV length of ${TEA_BLOCK_SIZE} bytes (${TEA_BLOCK_SIZE * 8} bits). +Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); + + if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255) + throw new OperationError(`Invalid number of rounds: ${rounds} + +Rounds must be an integer between 1 and 255. Standard XTEA uses 32 rounds.`); + + // Default IV to null bytes if empty (like AES) + const actualIv = iv.length === 0 ? new Array(TEA_BLOCK_SIZE).fill(0) : iv; + + input = Utils.convertToByteArray(input, inputType); + const output = encryptXTEA(input, key, actualIv, mode, padding, rounds); + return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output); + } + +} + +export default XTEAEncrypt; diff --git a/tests/operations/tests/TEA.mjs b/tests/operations/tests/TEA.mjs new file mode 100644 index 00000000..aa1043fe --- /dev/null +++ b/tests/operations/tests/TEA.mjs @@ -0,0 +1,566 @@ +/** + * TEA and XTEA cipher tests. + * + * Test vectors sourced from: + * - TEA: https://www.cix.co.uk/~klockstone/teavect.htm (Wheeler & Needham reference) + * - XTEA: https://github.com/golang/crypto/blob/master/xtea/xtea_test.go (Go standard library) + * Bouncy Castle XTEA test vectors (big-endian) + * + * @author Medjedtxm + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +/** + * TEA ECB Tests — Official test vectors from Wheeler & Needham + * + * From teavect.htm: TEA uses a fixed 32 cycles (64 Feistel rounds). + * Row 1: plaintext 00000000 00000000, key 00000000 00000000 00000000 00000000 + * -> ciphertext 41ea3a0a 94baa940 + */ +TestRegister.addTests([ + // ==================== TEA ECB TESTS ==================== + { + name: "TEA Encrypt: ECB, all-zero key and plaintext", + input: "0000000000000000", + expectedOutput: "41ea3a0a94baa940", + recipeConfig: [ + { + "op": "TEA Encrypt", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO" + ] + } + ], + }, + { + name: "TEA Decrypt: ECB, all-zero key", + input: "41ea3a0a94baa940", + expectedOutput: "0000000000000000", + recipeConfig: [ + { + "op": "TEA Decrypt", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO" + ] + } + ], + }, + { + name: "TEA Encrypt then Decrypt: round-trip ECB", + input: "48656c6c6f212121", + expectedOutput: "48656c6c6f212121", + recipeConfig: [ + { + "op": "TEA Encrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO" + ] + }, + { + "op": "TEA Decrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO" + ] + } + ], + }, + + // ==================== TEA CBC TEST ==================== + { + name: "TEA Encrypt then Decrypt: round-trip CBC with PKCS5", + input: "Hello TEA cipher!", + expectedOutput: "Hello TEA cipher!", + recipeConfig: [ + { + "op": "TEA Encrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": "0000000000000000"}, + "CBC", "Raw", "Hex", "PKCS5" + ] + }, + { + "op": "TEA Decrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": "0000000000000000"}, + "CBC", "Hex", "Raw", "PKCS5" + ] + } + ], + }, + + // ==================== TEA CTR TEST ==================== + { + name: "TEA Encrypt then Decrypt: round-trip CTR", + input: "Short", + expectedOutput: "Short", + recipeConfig: [ + { + "op": "TEA Encrypt", + "args": [ + {"option": "Hex", "string": "deadbeefdeadbeefdeadbeefdeadbeef"}, + {"option": "Hex", "string": "0102030405060708"}, + "CTR", "Raw", "Hex", "NO" + ] + }, + { + "op": "TEA Decrypt", + "args": [ + {"option": "Hex", "string": "deadbeefdeadbeefdeadbeefdeadbeef"}, + {"option": "Hex", "string": "0102030405060708"}, + "CTR", "Hex", "Raw", "NO" + ] + } + ], + }, + + // ==================== TEA CFB TEST ==================== + { + name: "TEA Encrypt then Decrypt: round-trip CFB", + input: "CFB mode testing with TEA", + expectedOutput: "CFB mode testing with TEA", + recipeConfig: [ + { + "op": "TEA Encrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": "aabbccddeeff0011"}, + "CFB", "Raw", "Hex", "NO" + ] + }, + { + "op": "TEA Decrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": "aabbccddeeff0011"}, + "CFB", "Hex", "Raw", "NO" + ] + } + ], + }, + + // ==================== TEA OFB TEST ==================== + { + name: "TEA Encrypt then Decrypt: round-trip OFB", + input: "OFB mode testing with TEA", + expectedOutput: "OFB mode testing with TEA", + recipeConfig: [ + { + "op": "TEA Encrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": "1122334455667788"}, + "OFB", "Raw", "Hex", "NO" + ] + }, + { + "op": "TEA Decrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": "1122334455667788"}, + "OFB", "Hex", "Raw", "NO" + ] + } + ], + }, + + // ==================== XTEA ECB TESTS ==================== + // Go standard library + Bouncy Castle test vectors (big-endian) + { + name: "XTEA Encrypt: ECB, sequential key, 'ABCDEFGH'", + input: "4142434445464748", + expectedOutput: "497df3d072612cb5", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + { + name: "XTEA Decrypt: ECB, sequential key", + input: "497df3d072612cb5", + expectedOutput: "4142434445464748", + recipeConfig: [ + { + "op": "XTEA Decrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + { + name: "XTEA Encrypt: ECB, sequential key, 'AAAAAAAA'", + input: "4141414141414141", + expectedOutput: "e78f2d13744341d8", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + { + name: "XTEA Encrypt: ECB, sequential key, plaintext 5a5b6e27", + input: "5a5b6e278948d77f", + expectedOutput: "4141414141414141", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + { + name: "XTEA Encrypt: ECB, all-zero key, 'ABCDEFGH'", + input: "4142434445464748", + expectedOutput: "a0390589f8b8efa5", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + { + name: "XTEA Encrypt: ECB, all-zero key, 'AAAAAAAA'", + input: "4141414141414141", + expectedOutput: "ed23375a821a8c2d", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + { + name: "XTEA Encrypt: ECB, all-zero key, all-zero plaintext", + input: "0000000000000000", + expectedOutput: "dee9d4d8f7131ed9", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + { + name: "XTEA Encrypt: ECB, all-zero key, sequential plaintext", + input: "0102030405060708", + expectedOutput: "065c1b8975c6a816", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + { + name: "XTEA Encrypt: ECB, pattern key, all-zero plaintext", + input: "0000000000000000", + expectedOutput: "1ff9a0261ac64264", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "0123456712345678234567893456789a"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + { + name: "XTEA Encrypt: ECB, pattern key, sequential plaintext", + input: "0102030405060708", + expectedOutput: "8c67155b2ef91ead", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "0123456712345678234567893456789a"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + { + name: "XTEA Decrypt: ECB, pattern key, sequential ciphertext", + input: "8c67155b2ef91ead", + expectedOutput: "0102030405060708", + recipeConfig: [ + { + "op": "XTEA Decrypt", + "args": [ + {"option": "Hex", "string": "0123456712345678234567893456789a"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + + // ==================== XTEA CBC TEST ==================== + { + name: "XTEA Encrypt then Decrypt: round-trip CBC with PKCS5", + input: "Hello XTEA cipher!", + expectedOutput: "Hello XTEA cipher!", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": "fedcba9876543210"}, + "CBC", "Raw", "Hex", "PKCS5", 32 + ] + }, + { + "op": "XTEA Decrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": "fedcba9876543210"}, + "CBC", "Hex", "Raw", "PKCS5", 32 + ] + } + ], + }, + + // ==================== XTEA OFB TEST ==================== + { + name: "XTEA Encrypt then Decrypt: round-trip OFB", + input: "Stream mode test", + expectedOutput: "Stream mode test", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "deadbeefdeadbeefdeadbeefdeadbeef"}, + {"option": "Hex", "string": "0102030405060708"}, + "OFB", "Raw", "Hex", "NO", 32 + ] + }, + { + "op": "XTEA Decrypt", + "args": [ + {"option": "Hex", "string": "deadbeefdeadbeefdeadbeefdeadbeef"}, + {"option": "Hex", "string": "0102030405060708"}, + "OFB", "Hex", "Raw", "NO", 32 + ] + } + ], + }, + + // ==================== XTEA CTR TEST ==================== + { + name: "XTEA Encrypt then Decrypt: round-trip CTR", + input: "CTR mode with XTEA", + expectedOutput: "CTR mode with XTEA", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "deadbeefdeadbeefdeadbeefdeadbeef"}, + {"option": "Hex", "string": "0000000000000001"}, + "CTR", "Raw", "Hex", "NO", 32 + ] + }, + { + "op": "XTEA Decrypt", + "args": [ + {"option": "Hex", "string": "deadbeefdeadbeefdeadbeefdeadbeef"}, + {"option": "Hex", "string": "0000000000000001"}, + "CTR", "Hex", "Raw", "NO", 32 + ] + } + ], + }, + + // ==================== XTEA CFB TEST ==================== + { + name: "XTEA Encrypt then Decrypt: round-trip CFB", + input: "CFB mode with XTEA cipher", + expectedOutput: "CFB mode with XTEA cipher", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": "aabbccddeeff0011"}, + "CFB", "Raw", "Hex", "NO", 32 + ] + }, + { + "op": "XTEA Decrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": "aabbccddeeff0011"}, + "CFB", "Hex", "Raw", "NO", 32 + ] + } + ], + }, + + // ==================== XTEA NON-DEFAULT ROUNDS TEST ==================== + { + name: "XTEA Encrypt then Decrypt: round-trip ECB with 16 rounds", + input: "4142434445464748", + expectedOutput: "4142434445464748", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 16 + ] + }, + { + "op": "XTEA Decrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 16 + ] + } + ], + }, + { + name: "XTEA Encrypt: ECB, 16 rounds differs from 32 rounds", + input: "4142434445464748", + expectedOutput: "497df3d072612cb5", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + + // ==================== EDGE CASES ==================== + { + name: "TEA Encrypt: empty input returns empty", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "TEA Encrypt", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO" + ] + } + ], + }, + { + name: "XTEA Encrypt: empty input returns empty", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Hex", "NO", 32 + ] + } + ], + }, + + // ==================== MULTI-BLOCK ECB TESTS ==================== + { + name: "TEA Encrypt then Decrypt: multi-block ECB with PKCS5", + input: "This is a longer message that spans multiple TEA blocks!", + expectedOutput: "This is a longer message that spans multiple TEA blocks!", + recipeConfig: [ + { + "op": "TEA Encrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": ""}, + "ECB", "Raw", "Hex", "PKCS5" + ] + }, + { + "op": "TEA Decrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Raw", "PKCS5" + ] + } + ], + }, + { + name: "XTEA Encrypt then Decrypt: multi-block ECB with PKCS5", + input: "This is a longer message that spans multiple XTEA blocks!", + expectedOutput: "This is a longer message that spans multiple XTEA blocks!", + recipeConfig: [ + { + "op": "XTEA Encrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": ""}, + "ECB", "Raw", "Hex", "PKCS5", 32 + ] + }, + { + "op": "XTEA Decrypt", + "args": [ + {"option": "Hex", "string": "0123456789abcdef0123456789abcdef"}, + {"option": "Hex", "string": ""}, + "ECB", "Hex", "Raw", "PKCS5", 32 + ] + } + ], + }, +]); From bf705e7d43af2959155ae5669f5bba78407ee7ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 12:56:04 +0100 Subject: [PATCH 03/25] chore (deps): bump the patch-updates group with 5 updates (#2654) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: GCHQDeveloper581 <63102987+GCHQDeveloper581@users.noreply.github.com> (added chromedriver update to 150.0.3) --- package-lock.json | 224 ++++++++++++++++++++++++++++------------------ package.json | 12 +-- 2 files changed, 143 insertions(+), 93 deletions(-) diff --git a/package-lock.json b/package-lock.json index 87a23efe..72159d44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "assert": "^2.1.0", "avsc": "^5.7.9", "bcryptjs": "^3.0.3", - "bignumber.js": "^11.1.4", + "bignumber.js": "^11.1.5", "blakejs": "^1.2.1", "bootstrap": "4.6.2", "bootstrap-colorpicker": "^3.4.0", @@ -118,13 +118,13 @@ "@codemirror/commands": "^6.10.4", "@codemirror/language": "^6.12.4", "@codemirror/search": "^6.7.1", - "@codemirror/state": "^6.5.4", - "@codemirror/view": "^6.43.4", + "@codemirror/state": "^6.7.1", + "@codemirror/view": "^6.43.6", "@puppeteer/browsers": "3.0.6", "autoprefixer": "^10.5.2", "babel-loader": "^10.1.1", "base64-loader": "^1.0.0", - "chromedriver": "^148.0.4", + "chromedriver": "^150.0.3", "cli-progress": "^3.12.0", "colors": "^1.4.0", "compression-webpack-plugin": "^12.0.0", @@ -158,9 +158,9 @@ "prompt": "^1.3.0", "sitemap": "^9.0.1", "terser": "^5.48.0", - "webpack": "^5.108.3", + "webpack": "^5.108.4", "webpack-bundle-analyzer": "^5.3.0", - "webpack-dev-server": "^5.2.5", + "webpack-dev-server": "^5.2.6", "webpack-node-externals": "^3.0.0", "worker-loader": "^3.0.8" }, @@ -1889,9 +1889,9 @@ } }, "node_modules/@codemirror/state": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.0.tgz", - "integrity": "sha512-Zbl9NyscLMZkfXPQnNAIIAFftidrA1UbcJEIMp24C0Bukc2I5T8wJS0wsXYsnDOqCFJUeJ1BITGNs5CqPDSmSg==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz", + "integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==", "dev": true, "license": "MIT", "dependencies": { @@ -1899,9 +1899,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.43.4", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.4.tgz", - "integrity": "sha512-YImu23iyKfncJzT7sRy+rEqEhSc8RhOHqDxwy4WzXRKJwYm6iwf/9OJk5ctCAdZ6yi2ZqaGEvmf55fSVqMDrgg==", + "version": "6.43.6", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.6.tgz", + "integrity": "sha512-EVunGSYN1wz1p75WY1s3Xg7t3i8Yol0kGZGizNdX9BUFgMFILYVe8/u6EVpo7Ff5PwbZuILb4QAq7IZoKzIEQA==", "dev": true, "license": "MIT", "dependencies": { @@ -5129,9 +5129,9 @@ } }, "node_modules/adm-zip": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", - "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.18.tgz", + "integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==", "dev": true, "license": "MIT", "engines": { @@ -5700,17 +5700,45 @@ } }, "node_modules/axios": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", - "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-loader": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.1.1.tgz", @@ -5883,9 +5911,9 @@ } }, "node_modules/bignumber.js": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-11.1.4.tgz", - "integrity": "sha512-AJ9dSeaUGj2xu7tEwmdqb51dqdb633xo4njI9K8ZFfcLrNr0XN8/EPkkZUNaF9fkCblGt2zVwZymesUdGynEkQ==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-11.1.5.tgz", + "integrity": "sha512-6WmzCNtUnfKpbozq+hOgWaZMMzORmYBwF1xZScyoIX3QRYWeKTtxxwDOW5tIz7C9BdjkIYHGTcelCLkXg0mndw==", "license": "MIT" }, "node_modules/binary-extensions": { @@ -6705,20 +6733,20 @@ } }, "node_modules/chromedriver": { - "version": "148.0.4", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-148.0.4.tgz", - "integrity": "sha512-3UyptFDG4YF1Pyv3fzn95s1CN4K3zCpHSmE6g+6J4f2u9KxxOYzrwN2GApVyM2z02hlbSqzo9Ajn2hMi7LnvCw==", + "version": "150.0.3", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-150.0.3.tgz", + "integrity": "sha512-i2L979d6YDTVVUDUPWXz75HGKKVhjNXo74gLiy/f8Adb5zHLU+h3ABdg8RRoiegtjJB0m9vUEiPTdecD0ifa3w==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@testim/chrome-version": "^1.1.4", - "adm-zip": "^0.5.17", - "axios": "^1.16.0", - "compare-versions": "^6.1.0", - "proxy-agent": "^8.0.1", - "proxy-from-env": "^2.0.0", - "tcp-port-used": "^1.0.2" + "adm-zip": "^0.5.18", + "axios": "^1.18.1", + "compare-versions": "^6.1.1", + "proxy-agent": "^8.0.2", + "proxy-from-env": "^2.1.0", + "tcp-port-used": "^1.0.3" }, "bin": { "chromedriver": "bin/chromedriver" @@ -10177,13 +10205,13 @@ } }, "node_modules/get-uri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-8.0.0.tgz", - "integrity": "sha512-CqtZlMKvfJeY0Zxv8wazDwXmSKmnMnsmNy8j8+wudi8EyG/pMUB1NqHc+Tv1QaNtpYsK9nOYjb7r7Ufu32RPSw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-8.0.1.tgz", + "integrity": "sha512-/5N/P4Lrh0p/mDwlDRi7Y1+P2o/OyzZI3l6Iz1Ov6XXwwm1y3RlZLuo3gVgML99djrEDtV980bBxSuOeHLk8ww==", "dev": true, "license": "MIT", "dependencies": { - "basic-ftp": "^5.2.0", + "basic-ftp": "^5.3.1", "data-uri-to-buffer": "8.0.0", "debug": "^4.3.4" }, @@ -11589,13 +11617,13 @@ } }, "node_modules/ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/ipaddr.js": { @@ -12207,15 +12235,15 @@ } }, "node_modules/is2": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", - "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.1.tgz", + "integrity": "sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA==", "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" + "ip-regex": "^2.1.0", + "is-url": "^1.2.2" }, "engines": { "node": ">=v0.10.0" @@ -14454,20 +14482,20 @@ } }, "node_modules/pac-proxy-agent": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-9.0.1.tgz", - "integrity": "sha512-3ZOSpLboOlpW4yp8Cuv21KlTULRqyJ5Uuad3wXpSKFrxdNgcHEyoa22GRaZ2UlgCVuR6z+5BiavtYVvbajL/Yw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-9.1.0.tgz", + "integrity": "sha512-1aU+1mpj3DrQPfo3gh+3Gap3G5x+axnMx1P/y0ZF2ch7kb2meyOCAH8K2k9d27ROsTE7TnAerzxqF9aon2jqnA==", "dev": true, "license": "MIT", "dependencies": { "agent-base": "9.0.0", "debug": "^4.3.4", - "get-uri": "8.0.0", - "http-proxy-agent": "9.0.0", - "https-proxy-agent": "9.0.0", + "get-uri": "8.0.1", + "http-proxy-agent": "9.1.0", + "https-proxy-agent": "9.1.0", "pac-resolver": "9.0.1", "quickjs-wasi": "^2.2.0", - "socks-proxy-agent": "10.0.0" + "socks-proxy-agent": "10.1.0" }, "engines": { "node": ">= 20" @@ -14484,28 +14512,30 @@ } }, "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.0.0.tgz", - "integrity": "sha512-FcF8VhXYLQcxWCnt/cCpT2apKsRDUGeVEeMqGu4HSTu29U8Yw0TLOjdYIlDsYk3IkUh+taX4IDWpPcCqKDhCjA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", "dev": true, "license": "MIT", "dependencies": { "agent-base": "9.0.0", - "debug": "^4.3.4" + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" }, "engines": { "node": ">= 20" } }, "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.0.0.tgz", - "integrity": "sha512-/MVmHp58WkOypgFhCLk4fzpPcFQvTJ/e6LBI7irpIO2HfxUbpmYoHF+KzipzJpxxzJu7aJNWQ0xojJ/dzV2G5g==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", + "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", "dev": true, "license": "MIT", "dependencies": { "agent-base": "9.0.0", - "debug": "^4.3.4" + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" }, "engines": { "node": ">= 20" @@ -15299,25 +15329,43 @@ } }, "node_modules/proxy-agent": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-8.0.1.tgz", - "integrity": "sha512-kccqGBqHZXR8onQhY/ganJjoO8QIKKRiFBhPOzbTZK16attzSZ/0XSmp9H7jrRxPKHjhGyx1q32lMPrJ3uLFgA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-8.0.2.tgz", + "integrity": "sha512-idLLRewuemWd7GH/BDJzGiB0dWGfT2SQs3jy6NtZtGWU9uPTTSdeC1/cdbqLwgzhfv027daGFuXX426e2Eg20A==", "dev": true, "license": "MIT", "dependencies": { "agent-base": "9.0.0", "debug": "^4.3.4", - "http-proxy-agent": "9.0.0", - "https-proxy-agent": "9.0.0", + "http-proxy-agent": "9.1.0", + "https-proxy-agent": "9.1.0", "lru-cache": "^7.14.1", - "pac-proxy-agent": "9.0.1", + "pac-proxy-agent": "9.1.0", "proxy-from-env": "^2.0.0", - "socks-proxy-agent": "10.0.0" + "socks-proxy-agent": "10.1.0" }, "engines": { "node": ">= 20" } }, + "node_modules/proxy-agent-negotiate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", + "integrity": "sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "kerberos": "^2.0.0" + }, + "peerDependenciesMeta": { + "kerberos": { + "optional": true + } + } + }, "node_modules/proxy-agent/node_modules/agent-base": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", @@ -15329,28 +15377,30 @@ } }, "node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.0.0.tgz", - "integrity": "sha512-FcF8VhXYLQcxWCnt/cCpT2apKsRDUGeVEeMqGu4HSTu29U8Yw0TLOjdYIlDsYk3IkUh+taX4IDWpPcCqKDhCjA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", "dev": true, "license": "MIT", "dependencies": { "agent-base": "9.0.0", - "debug": "^4.3.4" + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" }, "engines": { "node": ">= 20" } }, "node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.0.0.tgz", - "integrity": "sha512-/MVmHp58WkOypgFhCLk4fzpPcFQvTJ/e6LBI7irpIO2HfxUbpmYoHF+KzipzJpxxzJu7aJNWQ0xojJ/dzV2G5g==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", + "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", "dev": true, "license": "MIT", "dependencies": { "agent-base": "9.0.0", - "debug": "^4.3.4" + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" }, "engines": { "node": ">= 20" @@ -16795,9 +16845,9 @@ } }, "node_modules/socks-proxy-agent": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-10.0.0.tgz", - "integrity": "sha512-pyp2YR3mNxAMu0mGLtzs4g7O3uT4/9sQOLAKcViAkaS9fJWkud7nmaf6ZREFqQEi24IPkBcjfHjXhPTUWjo3uA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-10.1.0.tgz", + "integrity": "sha512-WlMj/67cEJ6MDI1OcsnjuYKDNDoyPCCYZ249kuuXPiMDw9F8PXkVaQ7YWu3siTydfQ/4BEZcvGzu+aYvz7dDCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -17235,14 +17285,14 @@ } }, "node_modules/tcp-port-used": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", - "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.3.tgz", + "integrity": "sha512-4CEQ3qRJYo+mtEbJ+OoQu3dF4TDkwaO3RDVC4UzP5cpAOIUWwuwPjD7sdxDFFqsMUjsXVVYBMlg/boAaloThMA==", "dev": true, "license": "MIT", "dependencies": { "debug": "4.3.1", - "is2": "^2.0.6" + "is2": "2.0.1" } }, "node_modules/tcp-port-used/node_modules/debug": { @@ -18099,9 +18149,9 @@ } }, "node_modules/webpack": { - "version": "5.108.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.3.tgz", - "integrity": "sha512-hOpaCHmQVVY66IVTjofnH14IgSdmod2aquSGHGuYig/OIdWge01Hk2Wt988DZcwXumFUT4+FvJY5N+ikl8o/ww==", + "version": "5.108.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.4.tgz", + "integrity": "sha512-yur8LyJoeiWh47dErD+Ok7vlbmDsJ3UbbRPAoxbGJ54WpE2y5yVo5G/inUzujnYgw3tPmBRdn+G7PoxXaYC33w==", "dev": true, "license": "MIT", "dependencies": { @@ -18226,9 +18276,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.5.tgz", - "integrity": "sha512-4wZtCquSuv9CKX8oybo+mqxtxZqWz47uM1Ch94lxowBztOhWCbhqvRbfC/mODOwxgV2brY+JGZpHq58/SuVFYg==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.6.tgz", + "integrity": "sha512-HNLRmamRvVavZQ+avceZifmv8hmdUjg43t6MI4SqJDwFdW7RPQwH5vzGhDRZSX59SgfbeHhLnq3g+uooWo7pVw==", "dev": true, "license": "MIT", "dependencies": { @@ -18250,7 +18300,7 @@ "graceful-fs": "^4.2.6", "http-proxy-middleware": "^2.0.9", "ipaddr.js": "^2.1.0", - "launch-editor": "^2.6.1", + "launch-editor": "^2.14.1", "open": "^10.0.3", "p-retry": "^6.2.0", "schema-utils": "^4.2.0", diff --git a/package.json b/package.json index 7940548d..7dae5369 100644 --- a/package.json +++ b/package.json @@ -47,13 +47,13 @@ "@codemirror/commands": "^6.10.4", "@codemirror/language": "^6.12.4", "@codemirror/search": "^6.7.1", - "@codemirror/state": "^6.5.4", - "@codemirror/view": "^6.43.4", + "@codemirror/state": "^6.7.1", + "@codemirror/view": "^6.43.6", "@puppeteer/browsers": "3.0.6", "autoprefixer": "^10.5.2", "babel-loader": "^10.1.1", "base64-loader": "^1.0.0", - "chromedriver": "^148.0.4", + "chromedriver": "^150.0.3", "cli-progress": "^3.12.0", "colors": "^1.4.0", "compression-webpack-plugin": "^12.0.0", @@ -87,9 +87,9 @@ "prompt": "^1.3.0", "sitemap": "^9.0.1", "terser": "^5.48.0", - "webpack": "^5.108.3", + "webpack": "^5.108.4", "webpack-bundle-analyzer": "^5.3.0", - "webpack-dev-server": "^5.2.5", + "webpack-dev-server": "^5.2.6", "webpack-node-externals": "^3.0.0", "worker-loader": "^3.0.8" }, @@ -105,7 +105,7 @@ "assert": "^2.1.0", "avsc": "^5.7.9", "bcryptjs": "^3.0.3", - "bignumber.js": "^11.1.4", + "bignumber.js": "^11.1.5", "blakejs": "^1.2.1", "bootstrap": "4.6.2", "bootstrap-colorpicker": "^3.4.0", From de784f6e52e7b4436766e448ca20a294e2b97287 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:13:41 +0100 Subject: [PATCH 04/25] chore (deps): bump the minor-updates group across 1 directory with 3 updates (#2669) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 34 +++++++++++++++++----------------- package.json | 6 +++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 72159d44..958b25b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,7 +72,7 @@ "loglevel-message-prefix": "^3.0.0", "lz-string": "^1.5.0", "lz4js": "^0.2.0", - "markdown-it": "^14.2.0", + "markdown-it": "^14.3.0", "moment": "^2.30.1", "moment-timezone": "^0.6.2", "ngeohash": "^0.6.3", @@ -86,7 +86,7 @@ "path": "^0.12.7", "popper.js": "^1.16.1", "process": "^0.11.10", - "protobufjs": "^8.6.5", + "protobufjs": "^8.7.0", "punycode.js": "^2.3.1", "qr-image": "^3.2.0", "reflect-metadata": "^0.2.2", @@ -157,7 +157,7 @@ "postcss-loader": "^8.2.1", "prompt": "^1.3.0", "sitemap": "^9.0.1", - "terser": "^5.48.0", + "terser": "^5.49.0", "webpack": "^5.108.4", "webpack-bundle-analyzer": "^5.3.0", "webpack-dev-server": "^5.2.6", @@ -12805,9 +12805,9 @@ "license": "MIT" }, "node_modules/linkify-it": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", - "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", "funding": [ { "type": "github", @@ -13134,9 +13134,9 @@ } }, "node_modules/markdown-it": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", - "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", + "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", "funding": [ { "type": "github", @@ -13150,8 +13150,8 @@ "license": "MIT", "dependencies": { "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.1", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" @@ -15293,9 +15293,9 @@ "license": "MIT" }, "node_modules/protobufjs": { - "version": "8.6.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.6.5.tgz", - "integrity": "sha512-zeE5LPpencAGXvsxyOYmEgJhxzHY8IsmPAFzstZVhDSVT8QH03q6gMZwZRaQGApevZbAL6u28ugs4CC+YKB2jQ==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.7.0.tgz", + "integrity": "sha512-uu52JNxLh3vsL7tXU/h0gDaywufvuUCTbGSi0NKQKBZ2ZopkmrWQJSQO/EFqzu/5YhiwgVM8rq/a/iVpx4eZ0g==", "license": "BSD-3-Clause", "dependencies": { "long": "^5.3.2" @@ -17321,9 +17321,9 @@ "license": "MIT" }, "node_modules/terser": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", - "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { diff --git a/package.json b/package.json index 7dae5369..8512412b 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "postcss-loader": "^8.2.1", "prompt": "^1.3.0", "sitemap": "^9.0.1", - "terser": "^5.48.0", + "terser": "^5.49.0", "webpack": "^5.108.4", "webpack-bundle-analyzer": "^5.3.0", "webpack-dev-server": "^5.2.6", @@ -156,7 +156,7 @@ "loglevel-message-prefix": "^3.0.0", "lz-string": "^1.5.0", "lz4js": "^0.2.0", - "markdown-it": "^14.2.0", + "markdown-it": "^14.3.0", "moment": "^2.30.1", "moment-timezone": "^0.6.2", "ngeohash": "^0.6.3", @@ -170,7 +170,7 @@ "path": "^0.12.7", "popper.js": "^1.16.1", "process": "^0.11.10", - "protobufjs": "^8.6.5", + "protobufjs": "^8.7.0", "punycode.js": "^2.3.1", "qr-image": "^3.2.0", "reflect-metadata": "^0.2.2", From 49b855c761b717b90f2a1db90458c4122c16291b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:24:22 +0100 Subject: [PATCH 05/25] chore (deps): bump the actions-dependencies group across 1 directory with 6 updates (#2668) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cla-close-stale.yml | 2 +- .github/workflows/pull_requests.yml | 6 +++--- .github/workflows/releases.yml | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cla-close-stale.yml b/.github/workflows/cla-close-stale.yml index 2bd2a78a..8f8cd11f 100644 --- a/.github/workflows/cla-close-stale.yml +++ b/.github/workflows/cla-close-stale.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Close stale unsigned-CLA PRs - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 #v10.3.0 + uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 #v10.4.0 with: # ---- Guards: only act on PRs carrying the CLA label ---- only-labels: 'awaiting cla' diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 8424e16d..aee3d22a 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -61,14 +61,14 @@ jobs: - name: Set up Docker Buildx if: success() - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 - name: Set up QEMU - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 - name: Production Image Build if: success() id: build-image - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: platforms: linux/amd64,linux/arm64,linux/arm/v7 diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index ae47bf4f..ae92cdf9 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -61,14 +61,14 @@ jobs: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 - name: Set up QEMU - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 - name: Image Metadata id: image-metadata - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | @@ -77,14 +77,14 @@ jobs: type=semver,pattern={{version}} - name: Log in to GHCR - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 with: registry: ${{ env.REGISTRY }} username: ${{ env.REGISTRY_USER }} password: ${{ env.REGISTRY_PASSWORD }} - name: Publish to GHCR - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . push: true From 81c18e2f699f2053596ba0dcfd6029ffc93b31c8 Mon Sep 17 00:00:00 2001 From: Michael Roberson Date: Thu, 16 Jul 2026 04:34:08 -0400 Subject: [PATCH 06/25] fix: remove stray punctuation from malformed To HTML Entity table values (#2660) --- src/core/operations/ToHTMLEntity.mjs | 6 ++--- tests/node/index.mjs | 1 + tests/node/tests/ToHTMLEntity.mjs | 33 ++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 tests/node/tests/ToHTMLEntity.mjs diff --git a/src/core/operations/ToHTMLEntity.mjs b/src/core/operations/ToHTMLEntity.mjs index f2a57a43..75a82018 100644 --- a/src/core/operations/ToHTMLEntity.mjs +++ b/src/core/operations/ToHTMLEntity.mjs @@ -405,7 +405,7 @@ const byteToEntity = { 989: "ϝ", 1008: "ϰ", 1009: "ϱ", - 1013: "ε,", + 1013: "ε", 1014: "϶", 1025: "Ё", 1026: "Ђ", @@ -660,7 +660,7 @@ const byteToEntity = { 8649: "⇉", 8650: "⇊", 8651: "⇋", - 8652: "⇌;", + 8652: "⇌", 8653: "⇍", 8654: "⇎", 8655: "⇏", @@ -782,7 +782,7 @@ const byteToEntity = { 8814: "≮", 8815: "≯", 8816: "≰", - 8817: "≱;", + 8817: "≱", 8818: "≲", 8819: "≳", 8820: "≴", diff --git a/tests/node/index.mjs b/tests/node/index.mjs index 360bf481..e53ca7b2 100644 --- a/tests/node/index.mjs +++ b/tests/node/index.mjs @@ -24,6 +24,7 @@ import "./tests/Dish.mjs"; import "./tests/NodeDish.mjs"; import "./tests/Utils.mjs"; import "./tests/Categories.mjs"; +import "./tests/ToHTMLEntity.mjs"; import "./tests/lib/BigIntUtils.mjs"; import "./tests/lib/ChartsProtocolPrototypePollution.mjs"; diff --git a/tests/node/tests/ToHTMLEntity.mjs b/tests/node/tests/ToHTMLEntity.mjs new file mode 100644 index 00000000..43d8cb49 --- /dev/null +++ b/tests/node/tests/ToHTMLEntity.mjs @@ -0,0 +1,33 @@ +import TestRegister from "../../lib/TestRegister.mjs"; +import ToHTMLEntity from "../../../src/core/operations/ToHTMLEntity.mjs"; +import it from "../assertionHandler.mjs"; +import assert from "assert"; + +TestRegister.addApiTests([ + it("To HTML Entity: every named entity in the table is well-formed", () => { + // "Convert all characters" emits an entity for every code point, so a + // correct table yields an unbroken stream of entity tokens. A malformed + // value such as "≱;" or "ε," leaves stray characters between + // tokens, which the walk below flags and reports with surrounding context. + let input = ""; + for (let cp = 0; cp <= 0xFFFF; cp++) { + if (cp >= 0xD800 && cp <= 0xDFFF) continue; // skip surrogate range + input += String.fromCodePoint(cp); + } + const output = new ToHTMLEntity().run(input, [true, "Named entities"]); + + const tokenRe = /&#[0-9]+;|&#x[0-9a-fA-F]+;|&[A-Za-z][A-Za-z0-9]*;/y; + const malformed = []; + let pos = 0; + while (pos < output.length) { + tokenRe.lastIndex = pos; + if (tokenRe.exec(output)) { + pos = tokenRe.lastIndex; + } else { + malformed.push(output.slice(Math.max(0, pos - 12), pos + 12)); + pos++; + } + } + assert.deepStrictEqual(malformed, [], `Malformed entity value(s) near: ${JSON.stringify(malformed)}`); + }), +]); From 9e59868b843e4ba3d7575a0b86fcfddd4abe49e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:21:11 +0100 Subject: [PATCH 07/25] chore (deps): bump websocket-driver from 0.7.4 to 0.7.5 (#2673) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 958b25b8..cde26b14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18434,9 +18434,9 @@ } }, "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", "dev": true, "license": "Apache-2.0", "dependencies": { From 0e2e1187192cb782e0b03ff45f8a1df93e855496 Mon Sep 17 00:00:00 2001 From: Imantas <11162236+giesmininkas@users.noreply.github.com> Date: Fri, 17 Jul 2026 19:09:44 +0300 Subject: [PATCH 08/25] Add COBS encoding/decoding operations (#2185) --- src/core/config/Categories.json | 4 +- src/core/lib/COBS.mjs | 86 ++++++ src/core/operations/FromCOBS.mjs | 38 +++ src/core/operations/ToCOBS.mjs | 38 +++ tests/operations/tests/COBS.mjs | 476 +++++++++++++++++++++++++++++++ 5 files changed, 641 insertions(+), 1 deletion(-) create mode 100644 src/core/lib/COBS.mjs create mode 100644 src/core/operations/FromCOBS.mjs create mode 100644 src/core/operations/ToCOBS.mjs create mode 100644 tests/operations/tests/COBS.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index c652ed48..6cf7e107 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -83,7 +83,9 @@ "Rison Decode", "To Modhex", "From Modhex", - "MIME Decoding" + "MIME Decoding", + "To COBS", + "From COBS" ] }, { diff --git a/src/core/lib/COBS.mjs b/src/core/lib/COBS.mjs new file mode 100644 index 00000000..e480fbcf --- /dev/null +++ b/src/core/lib/COBS.mjs @@ -0,0 +1,86 @@ +/** + * @author Imantas Lukenskas [imantas@lukenskas.dev] + * @copyright Imantas Lukenskas 2026 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; + +/** + * COBS-encode a byte array + * @param {Uint8Array} data + * @return {Uint8Array} + */ +export function toCobs(data) { + if (!data || data.length === 0) { + return new Uint8Array(); + } + + const output = []; + data = [0, ...data]; + + while (data.length > 0) { + const endIndex = data.findIndex((value, index) => value === 0 && index > 0); + + if ((endIndex < 0 || endIndex > 254) && data.length > 254) { + output.push(255); + output.push(...data.slice(1, 255)); + data = data.slice(255); + if (data.length !== 0) { + data = [0, ...data]; + } + } else if (endIndex < 0) { + output.push(data.length); + output.push(...data.slice(1)); + data = []; + } else { + output.push(endIndex); + output.push(...data.slice(1, endIndex)); + data = data.slice(endIndex); + } + } + + return output; +} + +/** + * COBS-decode a byte array + * @param {Uint8Array} data + * @return {Uint8Array} + */ +export function fromCobs(data) { + if (!data || data.length === 0) { + return new Uint8Array(); + } + + if (data.findIndex((value) => value === 0) >= 0) { + throw new OperationError("Could not decode from COBS: payload must not contain a 0x00 byte"); + } + + const output = []; + + while (data.length > 0) { + if (data[0] === 0xFF) { + output.push(...data.slice(1, 255)); + data = data.slice(255); + } else { + const nextZeroIndex = data[0]; + output.push(...data.slice(1, nextZeroIndex)); + data = data.slice(nextZeroIndex); + + let blockSize = data[0]; + while (data.length > 0) { + output.push(0, ...data.slice(1, blockSize)); + data = data.slice(blockSize); + + if (blockSize === 0xFF) { + break; + } + + blockSize = data[0]; + } + } + } + + return output; +} diff --git a/src/core/operations/FromCOBS.mjs b/src/core/operations/FromCOBS.mjs new file mode 100644 index 00000000..7347a72d --- /dev/null +++ b/src/core/operations/FromCOBS.mjs @@ -0,0 +1,38 @@ +/** + * @author Imantas Lukenskas [imantas@lukenskas.dev] + * @copyright Imantas Lukenskas 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {fromCobs} from "../lib/COBS.mjs"; + +/** + * From COBS operation + */ +class FromCOBS extends Operation { + /** + * FromCOBS constructor + */ + constructor() { + super(); + + this.name = "From COBS"; + this.module = "Default"; + this.description = "Decodes COBS encoded bytes"; + this.infoURL = "https://wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + return fromCobs(input); + } +} + +export default FromCOBS; diff --git a/src/core/operations/ToCOBS.mjs b/src/core/operations/ToCOBS.mjs new file mode 100644 index 00000000..9d1b5f36 --- /dev/null +++ b/src/core/operations/ToCOBS.mjs @@ -0,0 +1,38 @@ +/** + * @author Imantas Lukenskas [imantas@lukenskas.dev] + * @copyright Imantas Lukenskas 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import {toCobs} from "../lib/COBS.mjs"; + +/** + * To COBS operation + */ +class ToCOBS extends Operation { + /** + * ToCOBS constructor + */ + constructor() { + super(); + + this.name = "To COBS"; + this.module = "Default"; + this.description = "Encodes bytes in COBS format"; + this.infoURL = "https://wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + return toCobs(input); + } +} + +export default ToCOBS; diff --git a/tests/operations/tests/COBS.mjs b/tests/operations/tests/COBS.mjs new file mode 100644 index 00000000..a8c37c5f --- /dev/null +++ b/tests/operations/tests/COBS.mjs @@ -0,0 +1,476 @@ +/** + * @author Imantas Lukenskas [imantas@lukenskas.dev] + * @copyright Imantas Lukenskas 2026 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +// values in "COBS" testcases are taken from here: +// https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing + +TestRegister.addTests([ + { + "name": "To COBS Example #1", + "input": "00", + "expectedOutput": "01 01", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "To COBS Example #2", + "input": "00 00", + "expectedOutput": "01 01 01", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "To COBS Example #3", + "input": "00 11 00", + "expectedOutput": "01 02 11 01", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "To COBS Example #4", + "input": "11 22 00 33", + "expectedOutput": "03 11 22 02 33", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "To COBS Example #5", + "input": "11 22 33 44", + "expectedOutput": "05 11 22 33 44", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "To COBS Example #6", + "input": "11 00 00 00", + "expectedOutput": "02 11 01 01 01", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "To COBS Example #7", + "input": "01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe", + "expectedOutput": "ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "To COBS Example #8", + "input": "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe", + "expectedOutput": "01 ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "To COBS Example #9", + "input": "01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff", + "expectedOutput": "ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe 02 ff", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "To COBS Example #10", + "input": "02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 00", + "expectedOutput": "ff 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 01 01", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "To COBS Example #11", + "input": "03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 00 01", + "expectedOutput": "fe 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 02 01", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Example #1", + "input": "01 01", + "expectedOutput": "00", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Example #2", + "input": "01 01 01", + "expectedOutput": "00 00", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Example #3", + "input": "01 02 11 01", + "expectedOutput": "00 11 00", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Example #4", + "input": "03 11 22 02 33", + "expectedOutput": "11 22 00 33", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Example #5", + "input": "05 11 22 33 44", + "expectedOutput": "11 22 33 44", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Example #6", + "input": "02 11 01 01 01", + "expectedOutput": "11 00 00 00", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Example #7", + "input": "ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe", + "expectedOutput": "01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Example #8", + "input": "01 ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe", + "expectedOutput": "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Example #9", + "input": "ff 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe 02 ff", + "expectedOutput": "01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Example #10", + "input": "ff 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 01 01", + "expectedOutput": "02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 00", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Example #11", + "input": "fe 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 02 01", + "expectedOutput": "03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 00 01", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + }, + { + "op": "To Hex", + "args": ["Space"] + } + ] + }, + { + "name": "From COBS Invalid input #1", + "input": "00 48 45 4C 4C 4F", + "expectedOutput": "Could not decode from COBS: payload must not contain a 0x00 byte", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + } + ], + }, + { + "name": "From COBS Invalid input #2", + "input": "48 45 00 4C 4C 4F", + "expectedOutput": "Could not decode from COBS: payload must not contain a 0x00 byte", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + } + ], + }, + { + "name": "From COBS Invalid input #3", + "input": "48 45 4C 4C 4F 00", + "expectedOutput": "Could not decode from COBS: payload must not contain a 0x00 byte", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "From COBS", + "args": [] + } + ], + }, +]); From e5ee170e8d7c155f1f1afa2e12239fd9ece53612 Mon Sep 17 00:00:00 2001 From: p-leriche <7701190+p-leriche@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:24:44 +0100 Subject: [PATCH 09/25] Add Extended GCD operation (#2206) --- src/core/config/Categories.json | 2 - src/core/lib/BigIntUtils.mjs | 1 - src/core/operations/ExtendedGCD.mjs | 101 +++++++++++++++++++++++++ tests/operations/tests/ExtendedGCD.mjs | 78 +++++++++++++++++++ 4 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 src/core/operations/ExtendedGCD.mjs create mode 100644 tests/operations/tests/ExtendedGCD.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 6cf7e107..20846692 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -243,8 +243,6 @@ "Subtract", "Multiply", "Divide", - "Modular Exponentiation", - "Modular Inverse", "Extended GCD", "Mean", "Median", diff --git a/src/core/lib/BigIntUtils.mjs b/src/core/lib/BigIntUtils.mjs index 5ddd8786..a7187e90 100644 --- a/src/core/lib/BigIntUtils.mjs +++ b/src/core/lib/BigIntUtils.mjs @@ -70,4 +70,3 @@ export function modPow(base, exponent, modulus) { return result; } - diff --git a/src/core/operations/ExtendedGCD.mjs b/src/core/operations/ExtendedGCD.mjs new file mode 100644 index 00000000..88069c74 --- /dev/null +++ b/src/core/operations/ExtendedGCD.mjs @@ -0,0 +1,101 @@ +/** + * @author p-leriche [philip.leriche@cantab.net] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { parseBigInt, egcd } from "../lib/BigIntUtils.mjs"; + +/* ---------- operation class ---------- */ + +/** + * Extended GCD operation + */ +class ExtendedGCD extends Operation { + /** + * ExtendedGCD constructor + */ + constructor() { + super(); + + this.name = "Extended GCD"; + this.module = "Crypto"; + this.description = + "Computes the Extended Euclidean Algorithm for integers a and b.

" + + "Finds integers x and y (Bezout coefficients) such that:
" + + "a*x + b*y = gcd(a, b)

" + + "This is fundamental to many number theory algorithms including modular inverse, " + + "solving linear Diophantine equations, and cryptographic operations.

" + + "Input handling: If either a or b is left blank, " + + "its value is taken from the Input field."; + this.infoURL = "https://wikipedia.org/wiki/Extended_Euclidean_algorithm"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Value a", + type: "string", + value: "" + }, + { + name: "Value b", + type: "string", + value: "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [aStr, bStr] = args; + + // Trim everything so "" and " " count as empty + const aParam = aStr?.trim(); + const bParam = bStr?.trim(); + const inputVal = input?.trim(); + + let a, b; + + if (aParam && bParam) { + // Case 1: both values given as parameters + a = aParam; + b = bParam; + } else if (!aParam && bParam) { + // Case 2: a missing - take from input + a = inputVal; + b = bParam; + if (!a) throw new OperationError("Value a must be defined"); + } else if (aParam && !bParam) { + // Case 3: b missing - take from input + a = aParam; + b = inputVal; + if (!b) throw new OperationError("Value b must be defined"); + } else if (!aParam && !bParam) { + // Case 4: both values missing + throw new OperationError("Values a and b must be defined"); + } + + const aBI = parseBigInt(a, "Value a"); + const bBI = parseBigInt(b, "Value b"); + + const [g, x, y] = egcd(aBI, bBI); + const gcd = g < 0n ? -g : g; + + // Format output string bearing in mind that crypto-grade numbers + // may greatly exceed the line length. + let output = "gcd: " + gcd.toString() + "\n\n"; + output += "Bezout coefficients:\n"; + output += "x = " + x.toString() + "\n"; + output += "y = " + y.toString() + "\n\n"; + + return output; + } +} + +export default ExtendedGCD; diff --git a/tests/operations/tests/ExtendedGCD.mjs b/tests/operations/tests/ExtendedGCD.mjs new file mode 100644 index 00000000..fcc195ca --- /dev/null +++ b/tests/operations/tests/ExtendedGCD.mjs @@ -0,0 +1,78 @@ +/** + * Extended GCD tests. + * + * @author p-leriche [philip.leriche@cantab.net] + * + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Extended GCD: coprime numbers (3, 11)", + input: "", + expectedOutput: "gcd: 1\n\nBezout coefficients:\nx = 4\ny = -1\n\n", + recipeConfig: [ + { + op: "Extended GCD", + args: ["3", "11"], + }, + ], + }, + { + name: "Extended GCD: non-coprime numbers (240, 46)", + input: "", + expectedOutput: "gcd: 2\n\nBezout coefficients:\nx = -9\ny = 47\n\n", + recipeConfig: [ + { + op: "Extended GCD", + args: ["240", "46"], + }, + ], + }, + { + name: "Extended GCD: with zero (17, 0)", + input: "", + expectedOutput: "gcd: 17\n\nBezout coefficients:\nx = 1\ny = 0\n\n", + recipeConfig: [ + { + op: "Extended GCD", + args: ["17", "0"], + }, + ], + }, + { + name: "Extended GCD: hexadecimal input (0xFF, 0x11)", + input: "", + expectedOutput: "gcd: 17\n\nBezout coefficients:\nx = 0\ny = 1\n\n", + recipeConfig: [ + { + op: "Extended GCD", + args: ["0xFF", "0x11"], + }, + ], + }, + { + name: "Extended GCD: using input field for value a", + input: "42", + expectedOutput: "gcd: 7\n\nBezout coefficients:\nx = 1\ny = -1\n\n", + recipeConfig: [ + { + op: "Extended GCD", + args: ["", "35"], + }, + ], + }, + { + name: "Extended GCD: large numbers", + input: "", + expectedOutput: "gcd: 2\n\nBezout coefficients:\nx = 12703973750415151\ny = -1577756566311408967124629843\n\n", + recipeConfig: [ + { + op: "Extended GCD", + args: ["123456789012345678901234567890", "994064509324197316"], + }, + ], + }, +]); From d771ea2cc008c3a17d5032f04bf46da497b49636 Mon Sep 17 00:00:00 2001 From: Michael Roberson Date: Fri, 17 Jul 2026 12:36:55 -0400 Subject: [PATCH 10/25] Consolidate HTML entity tables into a single spec-generated source (#2645) (#2671) --- .gitignore | 1 + Gruntfile.js | 1 + .../config/scripts/generateHTMLEntities.mjs | 139 + .../config/scripts/htmlEntityOverrides.mjs | 86 + src/core/operations/FromHTMLEntity.mjs | 1460 +---------- src/core/operations/ToHTMLEntity.mjs | 1437 +---------- src/core/vendor/htmlEntities/entity.json | 2233 +++++++++++++++++ src/core/vendor/htmlEntities/entity.txt | 14 + tests/node/tests/ToHTMLEntity.mjs | 49 + tests/operations/tests/HTMLEntity.mjs | 126 + 10 files changed, 2658 insertions(+), 2888 deletions(-) create mode 100644 src/core/config/scripts/generateHTMLEntities.mjs create mode 100644 src/core/config/scripts/htmlEntityOverrides.mjs create mode 100644 src/core/vendor/htmlEntities/entity.json create mode 100644 src/core/vendor/htmlEntities/entity.txt create mode 100644 tests/operations/tests/HTMLEntity.mjs diff --git a/.gitignore b/.gitignore index a1e9dab6..45aee9aa 100755 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ build src/core/config/modules/* src/core/config/OperationConfig.json src/core/operations/index.mjs +src/core/lib/HTMLEntities.mjs src/node/config/OperationConfig.json src/node/index.mjs tests/operations/index.mjs diff --git a/Gruntfile.js b/Gruntfile.js index d379ba71..a963d1a3 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -367,6 +367,7 @@ module.exports = function (grunt) { command: chainCommands([ "echo '\n--- Regenerating config files. ---'", "echo [] > src/core/config/OperationConfig.json", + `node ${nodeFlags} src/core/config/scripts/generateHTMLEntities.mjs`, `node ${nodeFlags} src/core/config/scripts/generateOpsIndex.mjs`, `node ${nodeFlags} src/core/config/scripts/generateConfig.mjs`, "echo '--- Config scripts finished. ---\n'" diff --git a/src/core/config/scripts/generateHTMLEntities.mjs b/src/core/config/scripts/generateHTMLEntities.mjs new file mode 100644 index 00000000..6ff2bdbb --- /dev/null +++ b/src/core/config/scripts/generateHTMLEntities.mjs @@ -0,0 +1,139 @@ +/** + * This script automatically generates src/core/lib/HTMLEntities.mjs, the shared + * HTML entity lookup tables used by the "To HTML Entity" and "From HTML Entity" + * operations. + * + * The data is derived from the vendored WHATWG named character reference set + * (src/core/vendor/htmlEntities/entity.json, from + * https://html.spec.whatwg.org/entities.json) so the + * two operations cannot drift apart and every entity is spec-conformant: + * + * - HTML_ENTITY_REVERSE_LOOKUP (decode) is every single-code-point spec name. + * - HTML_ENTITY_LOOKUP (encode) picks one canonical name per code point via a + * deterministic tiebreak, overridden by htmlEntityOverrides.mjs where a + * specific historical name is preferred. + * + * @author roberson-io [michaelroberson@gmail.com] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +/* eslint no-console: ["off"] */ + +import path from "path"; +import fs from "fs"; +import process from "process"; +import { fileURLToPath } from "url"; +import { HTML_ENTITY_CANONICAL_OVERRIDES } from "./htmlEntityOverrides.mjs"; + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)); + +if (!fs.existsSync(path.join(process.cwd(), "src/core/lib"))) { + console.log("\nCWD: " + process.cwd()); + console.log("Error: generateHTMLEntities.mjs should be run from the project root"); + console.log("Example> node src/core/config/scripts/generateHTMLEntities.mjs"); + process.exit(1); +} + +const SPEC = JSON.parse(fs.readFileSync( + path.join(scriptDir, "..", "..", "vendor", "htmlEntities", "entity.json"), "utf8")); + +// Build code point -> [spec names] for single-code-point, semicolon-terminated +// references (the representable subset; multi-code-point entities are skipped). +const codePointNames = {}; +for (const [key, val] of Object.entries(SPEC)) { + if (!key.endsWith(";") || val.codepoints.length !== 1) continue; + const name = key.slice(1, -1); // strip leading "&" and trailing ";" + (codePointNames[val.codepoints[0]] ??= []).push(name); +} + +// Deterministic canonical-name tiebreak: prefer a lower-case name, then the +// shortest, then alphabetical. Overridden per code point where a specific +// historical name is preferred. +const isAllUpper = s => s === s.toUpperCase() && s !== s.toLowerCase(); + +/** + * Choose the single canonical entity name for a code point. + * + * @param {number} codePoint - the Unicode code point being encoded + * @param {string[]} names - all valid WHATWG names for that code point + * @returns {string} the canonical name to emit when encoding + */ +function canonicalName(codePoint, names) { + const override = HTML_ENTITY_CANONICAL_OVERRIDES[codePoint]; + if (override !== undefined) { + if (!names.includes(override)) + throw new Error(`Override &${override}; is not a spec name for code point ${codePoint} (spec: ${names})`); + return override; + } + return [...names].sort((a, b) => + (isAllUpper(a) - isAllUpper(b)) || + (a.length - b.length) || + (a < b ? -1 : a > b ? 1 : 0) + )[0]; +} + +const forward = {}; // code point -> canonical name (encode) +const reverse = {}; // name -> code point (decode, every spec name) +for (const [cpStr, names] of Object.entries(codePointNames)) { + const cp = Number(cpStr); + forward[cp] = canonicalName(cp, names); + for (const name of names) reverse[name] = cp; +} + +// Decode-only aliases = spec names that are not the canonical encode name. +const aliases = {}; +for (const [name, cp] of Object.entries(reverse)) { + if (forward[cp] !== name) aliases[name] = cp; +} + +// --- emit ----------------------------------------------------------------- +const fwdEntries = Object.keys(forward).map(Number).sort((a, b) => a - b) + .map(cp => ` ${cp}: "${forward[cp]}",`).join("\n").replace(/,$/, ""); +const aliasEntries = Object.keys(aliases).sort() + .map(n => ` ${JSON.stringify(n)}: ${aliases[n]},`).join("\n").replace(/,$/, ""); + +const code = `/** + * THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateHTMLEntities.mjs + * + * HTML entity lookup tables shared by the "To HTML Entity" and "From HTML Entity" + * operations, derived from the WHATWG named character reference set + * (https://html.spec.whatwg.org/entities.json). Do not edit by hand — change the + * vendored src/core/vendor/htmlEntities/entity.json or htmlEntityOverrides.mjs + * and regenerate. + * + * @author roberson-io [michaelroberson@gmail.com] + * @copyright Crown Copyright ${new Date().getUTCFullYear()} + * @license Apache-2.0 + */ + +/** + * Canonical lookup: Unicode code point -> entity name (without "&" and ";"), + * used for ENCODING. One canonical name per code point. + */ +export const HTML_ENTITY_LOOKUP = { +${fwdEntries} +}; + +/** + * Legacy / alias names that only DECODE (spelling variants, deprecated names, + * box-drawing aliases, etc.); not used for encoding. + */ +export const HTML_ENTITY_DECODE_ALIASES = { +${aliasEntries} +}; + +/** + * Derived reverse lookup: entity name -> code point, used for DECODING. Built + * from the canonical lookup plus the legacy aliases. + */ +export const HTML_ENTITY_REVERSE_LOOKUP = {...HTML_ENTITY_DECODE_ALIASES}; +for (const codePoint in HTML_ENTITY_LOOKUP) { + HTML_ENTITY_REVERSE_LOOKUP[HTML_ENTITY_LOOKUP[codePoint]] = Number(codePoint); +} +`; + +fs.writeFileSync(path.join(process.cwd(), "src/core/lib/HTMLEntities.mjs"), code); +console.log(`generateHTMLEntities: ${Object.keys(forward).length} encode names, ` + + `${Object.keys(reverse).length} decode names, ${Object.keys(aliases).length} aliases, ` + + `${Object.keys(HTML_ENTITY_CANONICAL_OVERRIDES).length} overrides applied.`); diff --git a/src/core/config/scripts/htmlEntityOverrides.mjs b/src/core/config/scripts/htmlEntityOverrides.mjs new file mode 100644 index 00000000..367f3e7d --- /dev/null +++ b/src/core/config/scripts/htmlEntityOverrides.mjs @@ -0,0 +1,86 @@ +/** + * Canonical entity-name overrides for generateHTMLEntities.mjs. + * + * The WHATWG named character reference set assigns MANY names to some code + * points (e.g. U+2211 is both ∑ and ∑), but does not designate a + * canonical one. The generator therefore needs a rule to pick a single name per + * code point for ENCODING. It uses a deterministic tiebreak (prefer a + * lower-case name, then the shortest, then alphabetical), which reproduces the + * historically-emitted name for ~1355 of the ~1414 encodable code points. + * + * This file pins the canonical name for the code points where the tiebreak + * would otherwise change the emitted entity. Every value here is still a valid + * WHATWG name for that code point (the generator asserts this) — these are + * editorial choices, not correctness fixes, kept so "To HTML Entity" output + * stays stable. Trim an entry to let the tiebreak decide instead. + * + * @author roberson-io [michaelroberson@gmail.com] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +/** + * @constant + * @type {Object.} + */ +export const HTML_ENTITY_CANONICAL_OVERRIDES = { + 124: "verbar", + 168: "uml", + 177: "plusmn", + 189: "frac12", + 247: "divide", + 711: "caron", + 728: "breve", + 937: "Omega", + 949: "epsilon", + 965: "upsilon", + 977: "thetasym", + 978: "upsih", + 981: "straightphi", + 8208: "hyphen", + 8214: "Verbar", + 8230: "hellip", + 8289: "ApplyFunction", + 8290: "InvisibleTimes", + 8291: "InvisibleComma", + 8459: "hamilt", + 8461: "quaternions", + 8463: "planck", + 8465: "image", + 8472: "weierp", + 8474: "rationals", + 8476: "real", + 8477: "reals", + 8484: "integers", + 8492: "bernou", + 8499: "phmmat", + 8500: "order", + 8501: "alefsym", + 8518: "DifferentialD", + 8519: "ExponentialE", + 8520: "ImaginaryI", + 8612: "LeftTeeArrow", + 8613: "UpTeeArrow", + 8615: "DownTeeArrow", + 8624: "lsh", + 8625: "rsh", + 8660: "hArr", + 8704: "forall", + 8711: "nabla", + 8712: "isin", + 8721: "sum", + 8723: "mnplus", + 8730: "radic", + 8750: "conint", + 8768: "wreath", + 8776: "asymp", + 8781: "asympeq", + 8784: "esdot", + 8788: "colone", + 8869: "perp", + 8896: "xwedge", + 8897: "xvee", + 8902: "sstarf", + 10536: "nesear", + 10537: "seswar" +}; diff --git a/src/core/operations/FromHTMLEntity.mjs b/src/core/operations/FromHTMLEntity.mjs index 4d09876d..e528b6a0 100644 --- a/src/core/operations/FromHTMLEntity.mjs +++ b/src/core/operations/FromHTMLEntity.mjs @@ -6,6 +6,7 @@ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; +import { HTML_ENTITY_REVERSE_LOOKUP } from "../lib/HTMLEntities.mjs"; /** * From HTML Entity operation @@ -51,7 +52,7 @@ class FromHTMLEntity extends Operation { output += input[i++]; // Add match - const bite = entityToByte[m[1]]; + const bite = HTML_ENTITY_REVERSE_LOOKUP[m[1]]; if (bite) { output += Utils.chr(bite); } else if (!bite && m[1][0] === "#" && m[1].length > 1 && /^#\d{1,6}$/.test(m[1])) { @@ -79,1461 +80,4 @@ class FromHTMLEntity extends Operation { } - -/** - * Lookup table to translate HTML entity codes to their byte values. - */ -const entityToByte = { - "Tab": 9, - "NewLine": 10, - "excl": 33, - "quot": 34, - "num": 35, - "dollar": 36, - "percnt": 37, - "amp": 38, - "apos": 39, - "lpar": 40, - "rpar": 41, - "ast": 42, - "plus": 43, - "comma": 44, - "period": 46, - "sol": 47, - "colon": 58, - "semi": 59, - "lt": 60, - "equals": 61, - "gt": 62, - "quest": 63, - "commat": 64, - "lsqb": 91, - "bsol": 92, - "rsqb": 93, - "Hat": 94, - "lowbar": 95, - "grave": 96, - "lcub": 123, - "verbar": 124, - "rcub": 125, - "nbsp": 160, - "iexcl": 161, - "cent": 162, - "pound": 163, - "curren": 164, - "yen": 165, - "brvbar": 166, - "sect": 167, - "uml": 168, - "copy": 169, - "ordf": 170, - "laquo": 171, - "not": 172, - "shy": 173, - "reg": 174, - "macr": 175, - "deg": 176, - "plusmn": 177, - "sup2": 178, - "sup3": 179, - "acute": 180, - "micro": 181, - "para": 182, - "middot": 183, - "cedil": 184, - "sup1": 185, - "ordm": 186, - "raquo": 187, - "frac14": 188, - "frac12": 189, - "frac34": 190, - "iquest": 191, - "Agrave": 192, - "Aacute": 193, - "Acirc": 194, - "Atilde": 195, - "Auml": 196, - "Aring": 197, - "AElig": 198, - "Ccedil": 199, - "Egrave": 200, - "Eacute": 201, - "Ecirc": 202, - "Euml": 203, - "Igrave": 204, - "Iacute": 205, - "Icirc": 206, - "Iuml": 207, - "ETH": 208, - "Ntilde": 209, - "Ograve": 210, - "Oacute": 211, - "Ocirc": 212, - "Otilde": 213, - "Ouml": 214, - "times": 215, - "Oslash": 216, - "Ugrave": 217, - "Uacute": 218, - "Ucirc": 219, - "Uuml": 220, - "Yacute": 221, - "THORN": 222, - "szlig": 223, - "agrave": 224, - "aacute": 225, - "acirc": 226, - "atilde": 227, - "auml": 228, - "aring": 229, - "aelig": 230, - "ccedil": 231, - "egrave": 232, - "eacute": 233, - "ecirc": 234, - "euml": 235, - "igrave": 236, - "iacute": 237, - "icirc": 238, - "iuml": 239, - "eth": 240, - "ntilde": 241, - "ograve": 242, - "oacute": 243, - "ocirc": 244, - "otilde": 245, - "ouml": 246, - "divide": 247, - "oslash": 248, - "ugrave": 249, - "uacute": 250, - "ucirc": 251, - "uuml": 252, - "yacute": 253, - "thorn": 254, - "yuml": 255, - "Amacr": 256, - "amacr": 257, - "Abreve": 258, - "abreve": 259, - "Aogon": 260, - "aogon": 261, - "Cacute": 262, - "cacute": 263, - "Ccirc": 264, - "ccirc": 265, - "Cdot": 266, - "cdot": 267, - "Ccaron": 268, - "ccaron": 269, - "Dcaron": 270, - "dcaron": 271, - "Dstrok": 272, - "dstrok": 273, - "Emacr": 274, - "emacr": 275, - "Edot": 278, - "edot": 279, - "Eogon": 280, - "eogon": 281, - "Ecaron": 282, - "ecaron": 283, - "Gcirc": 284, - "gcirc": 285, - "Gbreve": 286, - "gbreve": 287, - "Gdot": 288, - "gdot": 289, - "Gcedil": 290, - "Hcirc": 292, - "hcirc": 293, - "Hstrok": 294, - "hstrok": 295, - "Itilde": 296, - "itilde": 297, - "Imacr": 298, - "imacr": 299, - "Iogon": 302, - "iogon": 303, - "Idot": 304, - "imath": 305, - "IJlig": 306, - "ijlig": 307, - "Jcirc": 308, - "jcirc": 309, - "Kcedil": 310, - "kcedil": 311, - "kgreen": 312, - "Lacute": 313, - "lacute": 314, - "Lcedil": 315, - "lcedil": 316, - "Lcaron": 317, - "lcaron": 318, - "Lmidot": 319, - "lmidot": 320, - "Lstrok": 321, - "lstrok": 322, - "Nacute": 323, - "nacute": 324, - "Ncedil": 325, - "ncedil": 326, - "Ncaron": 327, - "ncaron": 328, - "napos": 329, - "ENG": 330, - "eng": 331, - "Omacr": 332, - "omacr": 333, - "Odblac": 336, - "odblac": 337, - "OElig": 338, - "oelig": 339, - "Racute": 340, - "racute": 341, - "Rcedil": 342, - "rcedil": 343, - "Rcaron": 344, - "rcaron": 345, - "Sacute": 346, - "sacute": 347, - "Scirc": 348, - "scirc": 349, - "Scedil": 350, - "scedil": 351, - "Scaron": 352, - "scaron": 353, - "Tcedil": 354, - "tcedil": 355, - "Tcaron": 356, - "tcaron": 357, - "Tstrok": 358, - "tstrok": 359, - "Utilde": 360, - "utilde": 361, - "Umacr": 362, - "umacr": 363, - "Ubreve": 364, - "ubreve": 365, - "Uring": 366, - "uring": 367, - "Udblac": 368, - "udblac": 369, - "Uogon": 370, - "uogon": 371, - "Wcirc": 372, - "wcirc": 373, - "Ycirc": 374, - "ycirc": 375, - "Yuml": 376, - "Zacute": 377, - "zacute": 378, - "Zdot": 379, - "zdot": 380, - "Zcaron": 381, - "zcaron": 382, - "fnof": 402, - "imped": 437, - "gacute": 501, - "jmath": 567, - "circ": 710, - "caron": 711, - "breve": 728, - "dot": 729, - "ring": 730, - "ogon": 731, - "tilde": 732, - "dblac": 733, - "DownBreve": 785, - "UnderBar": 818, - "Alpha": 913, - "Beta": 914, - "Gamma": 915, - "Delta": 916, - "Epsilon": 917, - "Zeta": 918, - "Eta": 919, - "Theta": 920, - "Iota": 921, - "Kappa": 922, - "Lambda": 923, - "Mu": 924, - "Nu": 925, - "Xi": 926, - "Omicron": 927, - "Pi": 928, - "Rho": 929, - "Sigma": 931, - "Tau": 932, - "Upsilon": 933, - "Phi": 934, - "Chi": 935, - "Psi": 936, - "Omega": 937, - "alpha": 945, - "beta": 946, - "gamma": 947, - "delta": 948, - "epsilon": 949, - "zeta": 950, - "eta": 951, - "theta": 952, - "iota": 953, - "kappa": 954, - "lambda": 955, - "mu": 956, - "nu": 957, - "xi": 958, - "omicron": 959, - "pi": 960, - "rho": 961, - "sigmaf": 962, - "sigma": 963, - "tau": 964, - "upsilon": 965, - "phi": 966, - "chi": 967, - "psi": 968, - "omega": 969, - "thetasym": 977, - "upsih": 978, - "straightphi": 981, - "piv": 982, - "Gammad": 988, - "gammad": 989, - "kappav": 1008, - "rhov": 1009, - "epsi": 1013, - "bepsi": 1014, - "IOcy": 1025, - "DJcy": 1026, - "GJcy": 1027, - "Jukcy": 1028, - "DScy": 1029, - "Iukcy": 1030, - "YIcy": 1031, - "Jsercy": 1032, - "LJcy": 1033, - "NJcy": 1034, - "TSHcy": 1035, - "KJcy": 1036, - "Ubrcy": 1038, - "DZcy": 1039, - "Acy": 1040, - "Bcy": 1041, - "Vcy": 1042, - "Gcy": 1043, - "Dcy": 1044, - "IEcy": 1045, - "ZHcy": 1046, - "Zcy": 1047, - "Icy": 1048, - "Jcy": 1049, - "Kcy": 1050, - "Lcy": 1051, - "Mcy": 1052, - "Ncy": 1053, - "Ocy": 1054, - "Pcy": 1055, - "Rcy": 1056, - "Scy": 1057, - "Tcy": 1058, - "Ucy": 1059, - "Fcy": 1060, - "KHcy": 1061, - "TScy": 1062, - "CHcy": 1063, - "SHcy": 1064, - "SHCHcy": 1065, - "HARDcy": 1066, - "Ycy": 1067, - "SOFTcy": 1068, - "Ecy": 1069, - "YUcy": 1070, - "YAcy": 1071, - "acy": 1072, - "bcy": 1073, - "vcy": 1074, - "gcy": 1075, - "dcy": 1076, - "iecy": 1077, - "zhcy": 1078, - "zcy": 1079, - "icy": 1080, - "jcy": 1081, - "kcy": 1082, - "lcy": 1083, - "mcy": 1084, - "ncy": 1085, - "ocy": 1086, - "pcy": 1087, - "rcy": 1088, - "scy": 1089, - "tcy": 1090, - "ucy": 1091, - "fcy": 1092, - "khcy": 1093, - "tscy": 1094, - "chcy": 1095, - "shcy": 1096, - "shchcy": 1097, - "hardcy": 1098, - "ycy": 1099, - "softcy": 1100, - "ecy": 1101, - "yucy": 1102, - "yacy": 1103, - "iocy": 1105, - "djcy": 1106, - "gjcy": 1107, - "jukcy": 1108, - "dscy": 1109, - "iukcy": 1110, - "yicy": 1111, - "jsercy": 1112, - "ljcy": 1113, - "njcy": 1114, - "tshcy": 1115, - "kjcy": 1116, - "ubrcy": 1118, - "dzcy": 1119, - "ensp": 8194, - "emsp": 8195, - "emsp13": 8196, - "emsp14": 8197, - "numsp": 8199, - "puncsp": 8200, - "thinsp": 8201, - "hairsp": 8202, - "ZeroWidthSpace": 8203, - "zwnj": 8204, - "zwj": 8205, - "lrm": 8206, - "rlm": 8207, - "hyphen": 8208, - "ndash": 8211, - "mdash": 8212, - "horbar": 8213, - "Verbar": 8214, - "lsquo": 8216, - "rsquo": 8217, - "sbquo": 8218, - "ldquo": 8220, - "rdquo": 8221, - "bdquo": 8222, - "dagger": 8224, - "Dagger": 8225, - "bull": 8226, - "nldr": 8229, - "hellip": 8230, - "permil": 8240, - "pertenk": 8241, - "prime": 8242, - "Prime": 8243, - "tprime": 8244, - "bprime": 8245, - "lsaquo": 8249, - "rsaquo": 8250, - "oline": 8254, - "caret": 8257, - "hybull": 8259, - "frasl": 8260, - "bsemi": 8271, - "qprime": 8279, - "MediumSpace": 8287, - "NoBreak": 8288, - "ApplyFunction": 8289, - "InvisibleTimes": 8290, - "InvisibleComma": 8291, - "euro": 8364, - "tdot": 8411, - "TripleDot": 8411, - "DotDot": 8412, - "Copf": 8450, - "incare": 8453, - "gscr": 8458, - "hamilt": 8459, - "Hfr": 8460, - "quaternions": 8461, - "planckh": 8462, - "planck": 8463, - "Iscr": 8464, - "image": 8465, - "Lscr": 8466, - "ell": 8467, - "Nopf": 8469, - "numero": 8470, - "copysr": 8471, - "weierp": 8472, - "Popf": 8473, - "rationals": 8474, - "Rscr": 8475, - "real": 8476, - "reals": 8477, - "rx": 8478, - "trade": 8482, - "integers": 8484, - "ohm": 8486, - "mho": 8487, - "Zfr": 8488, - "iiota": 8489, - "angst": 8491, - "bernou": 8492, - "Cfr": 8493, - "escr": 8495, - "Escr": 8496, - "Fscr": 8497, - "phmmat": 8499, - "order": 8500, - "alefsym": 8501, - "beth": 8502, - "gimel": 8503, - "daleth": 8504, - "CapitalDifferentialD": 8517, - "DifferentialD": 8518, - "ExponentialE": 8519, - "ImaginaryI": 8520, - "frac13": 8531, - "frac23": 8532, - "frac15": 8533, - "frac25": 8534, - "frac35": 8535, - "frac45": 8536, - "frac16": 8537, - "frac56": 8538, - "frac18": 8539, - "frac38": 8540, - "frac58": 8541, - "frac78": 8542, - "larr": 8592, - "uarr": 8593, - "rarr": 8594, - "darr": 8595, - "harr": 8596, - "varr": 8597, - "nwarr": 8598, - "nearr": 8599, - "searr": 8600, - "swarr": 8601, - "nlarr": 8602, - "nrarr": 8603, - "rarrw": 8605, - "Larr": 8606, - "Uarr": 8607, - "Rarr": 8608, - "Darr": 8609, - "larrtl": 8610, - "rarrtl": 8611, - "LeftTeeArrow": 8612, - "UpTeeArrow": 8613, - "map": 8614, - "DownTeeArrow": 8615, - "larrhk": 8617, - "rarrhk": 8618, - "larrlp": 8619, - "rarrlp": 8620, - "harrw": 8621, - "nharr": 8622, - "lsh": 8624, - "rsh": 8625, - "ldsh": 8626, - "rdsh": 8627, - "crarr": 8629, - "cularr": 8630, - "curarr": 8631, - "olarr": 8634, - "orarr": 8635, - "lharu": 8636, - "lhard": 8637, - "uharr": 8638, - "uharl": 8639, - "rharu": 8640, - "rhard": 8641, - "dharr": 8642, - "dharl": 8643, - "rlarr": 8644, - "udarr": 8645, - "lrarr": 8646, - "llarr": 8647, - "uuarr": 8648, - "rrarr": 8649, - "ddarr": 8650, - "lrhar": 8651, - "rlhar": 8652, - "nlArr": 8653, - "nhArr": 8654, - "nrArr": 8655, - "lArr": 8656, - "uArr": 8657, - "rArr": 8658, - "dArr": 8659, - "hArr": 8660, - "vArr": 8661, - "nwArr": 8662, - "neArr": 8663, - "seArr": 8664, - "swArr": 8665, - "lAarr": 8666, - "rAarr": 8667, - "zigrarr": 8669, - "larrb": 8676, - "rarrb": 8677, - "duarr": 8693, - "loarr": 8701, - "roarr": 8702, - "hoarr": 8703, - "forall": 8704, - "comp": 8705, - "part": 8706, - "exist": 8707, - "nexist": 8708, - "empty": 8709, - "nabla": 8711, - "isin": 8712, - "notin": 8713, - "ni": 8715, - "notni": 8716, - "prod": 8719, - "coprod": 8720, - "sum": 8721, - "minus": 8722, - "mnplus": 8723, - "plusdo": 8724, - "setmn": 8726, - "lowast": 8727, - "compfn": 8728, - "radic": 8730, - "prop": 8733, - "infin": 8734, - "angrt": 8735, - "ang": 8736, - "angmsd": 8737, - "angsph": 8738, - "mid": 8739, - "nmid": 8740, - "par": 8741, - "npar": 8742, - "and": 8743, - "or": 8744, - "cap": 8745, - "cup": 8746, - "int": 8747, - "Int": 8748, - "tint": 8749, - "conint": 8750, - "Conint": 8751, - "Cconint": 8752, - "cwint": 8753, - "cwconint": 8754, - "awconint": 8755, - "there4": 8756, - "becaus": 8757, - "ratio": 8758, - "Colon": 8759, - "minusd": 8760, - "mDDot": 8762, - "homtht": 8763, - "sim": 8764, - "bsim": 8765, - "ac": 8766, - "acd": 8767, - "wreath": 8768, - "nsim": 8769, - "esim": 8770, - "sime": 8771, - "nsime": 8772, - "cong": 8773, - "simne": 8774, - "ncong": 8775, - "asymp": 8776, - "nap": 8777, - "ape": 8778, - "apid": 8779, - "bcong": 8780, - "asympeq": 8781, - "bump": 8782, - "bumpe": 8783, - "esdot": 8784, - "eDot": 8785, - "efDot": 8786, - "erDot": 8787, - "colone": 8788, - "ecolon": 8789, - "ecir": 8790, - "cire": 8791, - "wedgeq": 8793, - "veeeq": 8794, - "trie": 8796, - "equest": 8799, - "ne": 8800, - "equiv": 8801, - "nequiv": 8802, - "le": 8804, - "ge": 8805, - "lE": 8806, - "gE": 8807, - "lnE": 8808, - "gnE": 8809, - "Lt": 8810, - "Gt": 8811, - "twixt": 8812, - "NotCupCap": 8813, - "nlt": 8814, - "ngt": 8815, - "nle": 8816, - "nge": 8817, - "lsim": 8818, - "gsim": 8819, - "nlsim": 8820, - "ngsim": 8821, - "lg": 8822, - "gl": 8823, - "ntlg": 8824, - "ntgl": 8825, - "pr": 8826, - "sc": 8827, - "prcue": 8828, - "sccue": 8829, - "prsim": 8830, - "scsim": 8831, - "npr": 8832, - "nsc": 8833, - "sub": 8834, - "sup": 8835, - "nsub": 8836, - "nsup": 8837, - "sube": 8838, - "supe": 8839, - "nsube": 8840, - "nsupe": 8841, - "subne": 8842, - "supne": 8843, - "cupdot": 8845, - "uplus": 8846, - "sqsub": 8847, - "sqsup": 8848, - "sqsube": 8849, - "sqsupe": 8850, - "sqcap": 8851, - "sqcup": 8852, - "oplus": 8853, - "ominus": 8854, - "otimes": 8855, - "osol": 8856, - "odot": 8857, - "ocir": 8858, - "oast": 8859, - "odash": 8861, - "plusb": 8862, - "minusb": 8863, - "timesb": 8864, - "sdotb": 8865, - "vdash": 8866, - "dashv": 8867, - "top": 8868, - "perp": 8869, - "models": 8871, - "vDash": 8872, - "Vdash": 8873, - "Vvdash": 8874, - "VDash": 8875, - "nvdash": 8876, - "nvDash": 8877, - "nVdash": 8878, - "nVDash": 8879, - "prurel": 8880, - "vltri": 8882, - "vrtri": 8883, - "ltrie": 8884, - "rtrie": 8885, - "origof": 8886, - "imof": 8887, - "mumap": 8888, - "hercon": 8889, - "intcal": 8890, - "veebar": 8891, - "barvee": 8893, - "angrtvb": 8894, - "lrtri": 8895, - "xwedge": 8896, - "xvee": 8897, - "xcap": 8898, - "xcup": 8899, - "diam": 8900, - "sdot": 8901, - "sstarf": 8902, - "divonx": 8903, - "bowtie": 8904, - "ltimes": 8905, - "rtimes": 8906, - "lthree": 8907, - "rthree": 8908, - "bsime": 8909, - "cuvee": 8910, - "cuwed": 8911, - "Sub": 8912, - "Sup": 8913, - "Cap": 8914, - "Cup": 8915, - "fork": 8916, - "epar": 8917, - "ltdot": 8918, - "gtdot": 8919, - "Ll": 8920, - "Gg": 8921, - "leg": 8922, - "gel": 8923, - "cuepr": 8926, - "cuesc": 8927, - "nprcue": 8928, - "nsccue": 8929, - "nsqsube": 8930, - "nsqsupe": 8931, - "lnsim": 8934, - "gnsim": 8935, - "prnsim": 8936, - "scnsim": 8937, - "nltri": 8938, - "nrtri": 8939, - "nltrie": 8940, - "nrtrie": 8941, - "vellip": 8942, - "ctdot": 8943, - "utdot": 8944, - "dtdot": 8945, - "disin": 8946, - "isinsv": 8947, - "isins": 8948, - "isindot": 8949, - "notinvc": 8950, - "notinvb": 8951, - "isinE": 8953, - "nisd": 8954, - "xnis": 8955, - "nis": 8956, - "notnivc": 8957, - "notnivb": 8958, - "barwed": 8965, - "Barwed": 8966, - "lceil": 8968, - "rceil": 8969, - "lfloor": 8970, - "rfloor": 8971, - "drcrop": 8972, - "dlcrop": 8973, - "urcrop": 8974, - "ulcrop": 8975, - "bnot": 8976, - "profline": 8978, - "profsurf": 8979, - "telrec": 8981, - "target": 8982, - "ulcorn": 8988, - "urcorn": 8989, - "dlcorn": 8990, - "drcorn": 8991, - "frown": 8994, - "smile": 8995, - "lang": 9001, - "rang": 9002, - "cylcty": 9005, - "profalar": 9006, - "topbot": 9014, - "ovbar": 9021, - "solbar": 9023, - "angzarr": 9084, - "lmoust": 9136, - "rmoust": 9137, - "tbrk": 9140, - "bbrk": 9141, - "bbrktbrk": 9142, - "OverParenthesis": 9180, - "UnderParenthesis": 9181, - "OverBrace": 9182, - "UnderBrace": 9183, - "trpezium": 9186, - "elinters": 9191, - "blank": 9251, - "oS": 9416, - "boxh": 9472, - "boxv": 9474, - "boxdr": 9484, - "boxdl": 9488, - "boxur": 9492, - "boxul": 9496, - "boxvr": 9500, - "boxvl": 9508, - "boxhd": 9516, - "boxhu": 9524, - "boxvh": 9532, - "boxH": 9552, - "boxV": 9553, - "boxdR": 9554, - "boxDr": 9555, - "boxDR": 9556, - "boxdL": 9557, - "boxDl": 9558, - "boxDL": 9559, - "boxuR": 9560, - "boxUr": 9561, - "boxUR": 9562, - "boxuL": 9563, - "boxUl": 9564, - "boxUL": 9565, - "boxvR": 9566, - "boxVr": 9567, - "boxVR": 9568, - "boxvL": 9569, - "boxVl": 9570, - "boxVL": 9571, - "boxHd": 9572, - "boxhD": 9573, - "boxHD": 9574, - "boxHu": 9575, - "boxhU": 9576, - "boxHU": 9577, - "boxvH": 9578, - "boxVh": 9579, - "boxVH": 9580, - "uhblk": 9600, - "lhblk": 9604, - "block": 9608, - "blk14": 9617, - "blk12": 9618, - "blk34": 9619, - "squ": 9633, - "squf": 9642, - "EmptyVerySmallSquare": 9643, - "rect": 9645, - "marker": 9646, - "fltns": 9649, - "xutri": 9651, - "utrif": 9652, - "utri": 9653, - "rtrif": 9656, - "rtri": 9657, - "xdtri": 9661, - "dtrif": 9662, - "dtri": 9663, - "ltrif": 9666, - "ltri": 9667, - "loz": 9674, - "cir": 9675, - "tridot": 9708, - "xcirc": 9711, - "ultri": 9720, - "urtri": 9721, - "lltri": 9722, - "EmptySmallSquare": 9723, - "FilledSmallSquare": 9724, - "starf": 9733, - "bigstar": 9733, - "star": 9734, - "phone": 9742, - "female": 9792, - "male": 9794, - "spades": 9824, - "clubs": 9827, - "hearts": 9829, - "diams": 9830, - "sung": 9834, - "flat": 9837, - "natur": 9838, - "sharp": 9839, - "check": 10003, - "cross": 10007, - "malt": 10016, - "sext": 10038, - "VerticalSeparator": 10072, - "lbbrk": 10098, - "rbbrk": 10099, - "lobrk": 10214, - "robrk": 10215, - "Lang": 10218, - "Rang": 10219, - "loang": 10220, - "roang": 10221, - "xlarr": 10229, - "xrarr": 10230, - "xharr": 10231, - "xlArr": 10232, - "xrArr": 10233, - "xhArr": 10234, - "xmap": 10236, - "dzigrarr": 10239, - "nvlArr": 10498, - "nvrArr": 10499, - "nvHarr": 10500, - "Map": 10501, - "lbarr": 10508, - "rbarr": 10509, - "lBarr": 10510, - "rBarr": 10511, - "RBarr": 10512, - "DDotrahd": 10513, - "UpArrowBar": 10514, - "DownArrowBar": 10515, - "Rarrtl": 10518, - "latail": 10521, - "ratail": 10522, - "lAtail": 10523, - "rAtail": 10524, - "larrfs": 10525, - "rarrfs": 10526, - "larrbfs": 10527, - "rarrbfs": 10528, - "nwarhk": 10531, - "nearhk": 10532, - "searhk": 10533, - "swarhk": 10534, - "nwnear": 10535, - "nesear": 10536, - "seswar": 10537, - "swnwar": 10538, - "rarrc": 10547, - "cudarrr": 10549, - "ldca": 10550, - "rdca": 10551, - "cudarrl": 10552, - "larrpl": 10553, - "curarrm": 10556, - "cularrp": 10557, - "rarrpl": 10565, - "harrcir": 10568, - "Uarrocir": 10569, - "lurdshar": 10570, - "ldrushar": 10571, - "LeftRightVector": 10574, - "RightUpDownVector": 10575, - "DownLeftRightVector": 10576, - "LeftUpDownVector": 10577, - "LeftVectorBar": 10578, - "RightVectorBar": 10579, - "RightUpVectorBar": 10580, - "RightDownVectorBar": 10581, - "DownLeftVectorBar": 10582, - "DownRightVectorBar": 10583, - "LeftUpVectorBar": 10584, - "LeftDownVectorBar": 10585, - "LeftTeeVector": 10586, - "RightTeeVector": 10587, - "RightUpTeeVector": 10588, - "RightDownTeeVector": 10589, - "DownLeftTeeVector": 10590, - "DownRightTeeVector": 10591, - "LeftUpTeeVector": 10592, - "LeftDownTeeVector": 10593, - "lHar": 10594, - "uHar": 10595, - "rHar": 10596, - "dHar": 10597, - "luruhar": 10598, - "ldrdhar": 10599, - "ruluhar": 10600, - "rdldhar": 10601, - "lharul": 10602, - "llhard": 10603, - "rharul": 10604, - "lrhard": 10605, - "udhar": 10606, - "duhar": 10607, - "RoundImplies": 10608, - "erarr": 10609, - "simrarr": 10610, - "larrsim": 10611, - "rarrsim": 10612, - "rarrap": 10613, - "ltlarr": 10614, - "gtrarr": 10616, - "subrarr": 10617, - "suplarr": 10619, - "lfisht": 10620, - "rfisht": 10621, - "ufisht": 10622, - "dfisht": 10623, - "lopar": 10629, - "ropar": 10630, - "lbrke": 10635, - "rbrke": 10636, - "lbrkslu": 10637, - "rbrksld": 10638, - "lbrksld": 10639, - "rbrkslu": 10640, - "langd": 10641, - "rangd": 10642, - "lparlt": 10643, - "rpargt": 10644, - "gtlPar": 10645, - "ltrPar": 10646, - "vzigzag": 10650, - "vangrt": 10652, - "angrtvbd": 10653, - "ange": 10660, - "range": 10661, - "dwangle": 10662, - "uwangle": 10663, - "angmsdaa": 10664, - "angmsdab": 10665, - "angmsdac": 10666, - "angmsdad": 10667, - "angmsdae": 10668, - "angmsdaf": 10669, - "angmsdag": 10670, - "angmsdah": 10671, - "bemptyv": 10672, - "demptyv": 10673, - "cemptyv": 10674, - "raemptyv": 10675, - "laemptyv": 10676, - "ohbar": 10677, - "omid": 10678, - "opar": 10679, - "operp": 10681, - "olcross": 10683, - "odsold": 10684, - "olcir": 10686, - "ofcir": 10687, - "olt": 10688, - "ogt": 10689, - "cirscir": 10690, - "cirE": 10691, - "solb": 10692, - "bsolb": 10693, - "boxbox": 10697, - "trisb": 10701, - "rtriltri": 10702, - "LeftTriangleBar": 10703, - "RightTriangleBar": 10704, - "race": 10714, - "iinfin": 10716, - "infintie": 10717, - "nvinfin": 10718, - "eparsl": 10723, - "smeparsl": 10724, - "eqvparsl": 10725, - "lozf": 10731, - "RuleDelayed": 10740, - "dsol": 10742, - "xodot": 10752, - "xoplus": 10753, - "xotime": 10754, - "xuplus": 10756, - "xsqcup": 10758, - "qint": 10764, - "fpartint": 10765, - "cirfnint": 10768, - "awint": 10769, - "rppolint": 10770, - "scpolint": 10771, - "npolint": 10772, - "pointint": 10773, - "quatint": 10774, - "intlarhk": 10775, - "pluscir": 10786, - "plusacir": 10787, - "simplus": 10788, - "plusdu": 10789, - "plussim": 10790, - "plustwo": 10791, - "mcomma": 10793, - "minusdu": 10794, - "loplus": 10797, - "roplus": 10798, - "Cross": 10799, - "timesd": 10800, - "timesbar": 10801, - "smashp": 10803, - "lotimes": 10804, - "rotimes": 10805, - "otimesas": 10806, - "Otimes": 10807, - "odiv": 10808, - "triplus": 10809, - "triminus": 10810, - "tritime": 10811, - "iprod": 10812, - "amalg": 10815, - "capdot": 10816, - "ncup": 10818, - "ncap": 10819, - "capand": 10820, - "cupor": 10821, - "cupcap": 10822, - "capcup": 10823, - "cupbrcap": 10824, - "capbrcup": 10825, - "cupcup": 10826, - "capcap": 10827, - "ccups": 10828, - "ccaps": 10829, - "ccupssm": 10832, - "And": 10835, - "Or": 10836, - "andand": 10837, - "oror": 10838, - "orslope": 10839, - "andslope": 10840, - "andv": 10842, - "orv": 10843, - "andd": 10844, - "ord": 10845, - "wedbar": 10847, - "sdote": 10854, - "simdot": 10858, - "congdot": 10861, - "easter": 10862, - "apacir": 10863, - "apE": 10864, - "eplus": 10865, - "pluse": 10866, - "Esim": 10867, - "Colone": 10868, - "Equal": 10869, - "eDDot": 10871, - "equivDD": 10872, - "ltcir": 10873, - "gtcir": 10874, - "ltquest": 10875, - "gtquest": 10876, - "les": 10877, - "ges": 10878, - "lesdot": 10879, - "gesdot": 10880, - "lesdoto": 10881, - "gesdoto": 10882, - "lesdotor": 10883, - "gesdotol": 10884, - "lap": 10885, - "gap": 10886, - "lne": 10887, - "gne": 10888, - "lnap": 10889, - "gnap": 10890, - "lEg": 10891, - "gEl": 10892, - "lsime": 10893, - "gsime": 10894, - "lsimg": 10895, - "gsiml": 10896, - "lgE": 10897, - "glE": 10898, - "lesges": 10899, - "gesles": 10900, - "els": 10901, - "egs": 10902, - "elsdot": 10903, - "egsdot": 10904, - "el": 10905, - "eg": 10906, - "siml": 10909, - "simg": 10910, - "simlE": 10911, - "simgE": 10912, - "LessLess": 10913, - "GreaterGreater": 10914, - "glj": 10916, - "gla": 10917, - "ltcc": 10918, - "gtcc": 10919, - "lescc": 10920, - "gescc": 10921, - "smt": 10922, - "lat": 10923, - "smte": 10924, - "late": 10925, - "bumpE": 10926, - "pre": 10927, - "sce": 10928, - "prE": 10931, - "scE": 10932, - "prnE": 10933, - "scnE": 10934, - "prap": 10935, - "scap": 10936, - "prnap": 10937, - "scnap": 10938, - "Pr": 10939, - "Sc": 10940, - "subdot": 10941, - "supdot": 10942, - "subplus": 10943, - "supplus": 10944, - "submult": 10945, - "supmult": 10946, - "subedot": 10947, - "supedot": 10948, - "subE": 10949, - "supE": 10950, - "subsim": 10951, - "supsim": 10952, - "subnE": 10955, - "supnE": 10956, - "csub": 10959, - "csup": 10960, - "csube": 10961, - "csupe": 10962, - "subsup": 10963, - "supsub": 10964, - "subsub": 10965, - "supsup": 10966, - "suphsub": 10967, - "supdsub": 10968, - "forkv": 10969, - "topfork": 10970, - "mlcp": 10971, - "Dashv": 10980, - "Vdashl": 10982, - "Barv": 10983, - "vBar": 10984, - "vBarv": 10985, - "Vbar": 10987, - "Not": 10988, - "bNot": 10989, - "rnmid": 10990, - "cirmid": 10991, - "midcir": 10992, - "topcir": 10993, - "nhpar": 10994, - "parsim": 10995, - "parsl": 11005, - "fflig": 64256, - "filig": 64257, - "fllig": 64258, - "ffilig": 64259, - "ffllig": 64260, - "Ascr": 119964, - "Cscr": 119966, - "Dscr": 119967, - "Gscr": 119970, - "Jscr": 119973, - "Kscr": 119974, - "Nscr": 119977, - "Oscr": 119978, - "Pscr": 119979, - "Qscr": 119980, - "Sscr": 119982, - "Tscr": 119983, - "Uscr": 119984, - "Vscr": 119985, - "Wscr": 119986, - "Xscr": 119987, - "Yscr": 119988, - "Zscr": 119989, - "ascr": 119990, - "bscr": 119991, - "cscr": 119992, - "dscr": 119993, - "fscr": 119995, - "hscr": 119997, - "iscr": 119998, - "jscr": 119999, - "kscr": 120000, - "lscr": 120001, - "mscr": 120002, - "nscr": 120003, - "pscr": 120005, - "qscr": 120006, - "rscr": 120007, - "sscr": 120008, - "tscr": 120009, - "uscr": 120010, - "vscr": 120011, - "wscr": 120012, - "xscr": 120013, - "yscr": 120014, - "zscr": 120015, - "Afr": 120068, - "Bfr": 120069, - "Dfr": 120071, - "Efr": 120072, - "Ffr": 120073, - "Gfr": 120074, - "Jfr": 120077, - "Kfr": 120078, - "Lfr": 120079, - "Mfr": 120080, - "Nfr": 120081, - "Ofr": 120082, - "Pfr": 120083, - "Qfr": 120084, - "Sfr": 120086, - "Tfr": 120087, - "Ufr": 120088, - "Vfr": 120089, - "Wfr": 120090, - "Xfr": 120091, - "Yfr": 120092, - "afr": 120094, - "bfr": 120095, - "cfr": 120096, - "dfr": 120097, - "efr": 120098, - "ffr": 120099, - "gfr": 120100, - "hfr": 120101, - "ifr": 120102, - "jfr": 120103, - "kfr": 120104, - "lfr": 120105, - "mfr": 120106, - "nfr": 120107, - "ofr": 120108, - "pfr": 120109, - "qfr": 120110, - "rfr": 120111, - "sfr": 120112, - "tfr": 120113, - "ufr": 120114, - "vfr": 120115, - "wfr": 120116, - "xfr": 120117, - "yfr": 120118, - "zfr": 120119, - "Aopf": 120120, - "Bopf": 120121, - "Dopf": 120123, - "Eopf": 120124, - "Fopf": 120125, - "Gopf": 120126, - "Iopf": 120128, - "Jopf": 120129, - "Kopf": 120130, - "Lopf": 120131, - "Mopf": 120132, - "Oopf": 120134, - "Sopf": 120138, - "Topf": 120139, - "Uopf": 120140, - "Vopf": 120141, - "Wopf": 120142, - "Xopf": 120143, - "Yopf": 120144, - "aopf": 120146, - "bopf": 120147, - "copf": 120148, - "dopf": 120149, - "eopf": 120150, - "fopf": 120151, - "gopf": 120152, - "hopf": 120153, - "iopf": 120154, - "jopf": 120155, - "kopf": 120156, - "lopf": 120157, - "mopf": 120158, - "nopf": 120159, - "oopf": 120160, - "popf": 120161, - "qopf": 120162, - "ropf": 120163, - "sopf": 120164, - "topf": 120165, - "uopf": 120166, - "vopf": 120167, - "wopf": 120168, - "xopf": 120169, - "yopf": 120170, - "zopf": 120171 -}; - export default FromHTMLEntity; diff --git a/src/core/operations/ToHTMLEntity.mjs b/src/core/operations/ToHTMLEntity.mjs index 75a82018..c9a2d9ba 100644 --- a/src/core/operations/ToHTMLEntity.mjs +++ b/src/core/operations/ToHTMLEntity.mjs @@ -6,6 +6,7 @@ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; +import { HTML_ENTITY_LOOKUP } from "../lib/HTMLEntities.mjs"; /** * To HTML Entity operation @@ -52,26 +53,28 @@ class ToHTMLEntity extends Operation { let output = ""; for (let i = 0; i < charcodes.length; i++) { + const named = charcodes[i] in HTML_ENTITY_LOOKUP ? + "&" + HTML_ENTITY_LOOKUP[charcodes[i]] + ";" : null; if (convertAll && numeric) { output += "&#" + charcodes[i] + ";"; } else if (convertAll && hexa) { output += "&#x" + Utils.hex(charcodes[i]) + ";"; } else if (convertAll) { - output += byteToEntity[charcodes[i]] || "&#" + charcodes[i] + ";"; + output += named || "&#" + charcodes[i] + ";"; } else if (numeric) { - if (charcodes[i] > 255 || charcodes[i] in byteToEntity) { + if (charcodes[i] > 255 || named) { output += "&#" + charcodes[i] + ";"; } else { output += Utils.chr(charcodes[i]); } } else if (hexa) { - if (charcodes[i] > 255 || charcodes[i] in byteToEntity) { + if (charcodes[i] > 255 || named) { output += "&#x" + Utils.hex(charcodes[i]) + ";"; } else { output += Utils.chr(charcodes[i]); } } else { - output += byteToEntity[charcodes[i]] || ( + output += named || ( charcodes[i] > 255 ? "&#" + charcodes[i] + ";" : Utils.chr(charcodes[i]) @@ -83,1430 +86,4 @@ class ToHTMLEntity extends Operation { } -/** - * Lookup table to translate byte values to their HTML entity codes. - */ -const byteToEntity = { - 9: " ", - 10: " ", - 33: "!", - 34: """, - 35: "#", - 36: "$", - 37: "%", - 38: "&", - 39: "'", - 40: "(", - 41: ")", - 42: "*", - 43: "+", - 44: ",", - 46: ".", - 47: "/", - 58: ":", - 59: ";", - 60: "<", - 61: "=", - 62: ">", - 63: "?", - 64: "@", - 91: "[", - 92: "\", - 93: "]", - 94: "^", - 95: "_", - 96: "`", - 123: "{", - 124: "|", - 125: "}", - 160: " ", - 161: "¡", - 162: "¢", - 163: "£", - 164: "¤", - 165: "¥", - 166: "¦", - 167: "§", - 168: "¨", - 169: "©", - 170: "ª", - 171: "«", - 172: "¬", - 173: "­", - 174: "®", - 175: "¯", - 176: "°", - 177: "±", - 178: "²", - 179: "³", - 180: "´", - 181: "µ", - 182: "¶", - 183: "·", - 184: "¸", - 185: "¹", - 186: "º", - 187: "»", - 188: "¼", - 189: "½", - 190: "¾", - 191: "¿", - 192: "À", - 193: "Á", - 194: "Â", - 195: "Ã", - 196: "Ä", - 197: "Å", - 198: "Æ", - 199: "Ç", - 200: "È", - 201: "É", - 202: "Ê", - 203: "Ë", - 204: "Ì", - 205: "Í", - 206: "Î", - 207: "Ï", - 208: "Ð", - 209: "Ñ", - 210: "Ò", - 211: "Ó", - 212: "Ô", - 213: "Õ", - 214: "Ö", - 215: "×", - 216: "Ø", - 217: "Ù", - 218: "Ú", - 219: "Û", - 220: "Ü", - 221: "Ý", - 222: "Þ", - 223: "ß", - 224: "à", - 225: "á", - 226: "â", - 227: "ã", - 228: "ä", - 229: "å", - 230: "æ", - 231: "ç", - 232: "è", - 233: "é", - 234: "ê", - 235: "ë", - 236: "ì", - 237: "í", - 238: "î", - 239: "ï", - 240: "ð", - 241: "ñ", - 242: "ò", - 243: "ó", - 244: "ô", - 245: "õ", - 246: "ö", - 247: "÷", - 248: "ø", - 249: "ù", - 250: "ú", - 251: "û", - 252: "ü", - 253: "ý", - 254: "þ", - 255: "ÿ", - 256: "Ā", - 257: "ā", - 258: "Ă", - 259: "ă", - 260: "Ą", - 261: "ą", - 262: "Ć", - 263: "ć", - 264: "Ĉ", - 265: "ĉ", - 266: "Ċ", - 267: "ċ", - 268: "Č", - 269: "č", - 270: "Ď", - 271: "ď", - 272: "Đ", - 273: "đ", - 274: "Ē", - 275: "ē", - 278: "Ė", - 279: "ė", - 280: "Ę", - 281: "ę", - 282: "Ě", - 283: "ě", - 284: "Ĝ", - 285: "ĝ", - 286: "Ğ", - 287: "ğ", - 288: "Ġ", - 289: "ġ", - 290: "Ģ", - 292: "Ĥ", - 293: "ĥ", - 294: "Ħ", - 295: "ħ", - 296: "Ĩ", - 297: "ĩ", - 298: "Ī", - 299: "ī", - 302: "Į", - 303: "į", - 304: "İ", - 305: "ı", - 306: "IJ", - 307: "ij", - 308: "Ĵ", - 309: "ĵ", - 310: "Ķ", - 311: "ķ", - 312: "ĸ", - 313: "Ĺ", - 314: "ĺ", - 315: "Ļ", - 316: "ļ", - 317: "Ľ", - 318: "ľ", - 319: "Ŀ", - 320: "ŀ", - 321: "Ł", - 322: "ł", - 323: "Ń", - 324: "ń", - 325: "Ņ", - 326: "ņ", - 327: "Ň", - 328: "ň", - 329: "ʼn", - 330: "Ŋ", - 331: "ŋ", - 332: "Ō", - 333: "ō", - 336: "Ő", - 337: "ő", - 338: "Œ", - 339: "œ", - 340: "Ŕ", - 341: "ŕ", - 342: "Ŗ", - 343: "ŗ", - 344: "Ř", - 345: "ř", - 346: "Ś", - 347: "ś", - 348: "Ŝ", - 349: "ŝ", - 350: "Ş", - 351: "ş", - 352: "Š", - 353: "š", - 354: "Ţ", - 355: "ţ", - 356: "Ť", - 357: "ť", - 358: "Ŧ", - 359: "ŧ", - 360: "Ũ", - 361: "ũ", - 362: "Ū", - 363: "ū", - 364: "Ŭ", - 365: "ŭ", - 366: "Ů", - 367: "ů", - 368: "Ű", - 369: "ű", - 370: "Ų", - 371: "ų", - 372: "Ŵ", - 373: "ŵ", - 374: "Ŷ", - 375: "ŷ", - 376: "Ÿ", - 377: "Ź", - 378: "ź", - 379: "Ż", - 380: "ż", - 381: "Ž", - 382: "ž", - 402: "ƒ", - 437: "Ƶ", - 501: "ǵ", - 567: "ȷ", - 710: "ˆ", - 711: "ˇ", - 728: "˘", - 729: "˙", - 730: "˚", - 731: "˛", - 732: "˜", - 785: "̑", - 818: "_", - 913: "Α", - 914: "Β", - 915: "Γ", - 916: "Δ", - 917: "Ε", - 918: "Ζ", - 919: "Η", - 920: "Θ", - 921: "Ι", - 922: "Κ", - 923: "Λ", - 924: "Μ", - 925: "Ν", - 926: "Ξ", - 927: "Ο", - 928: "Π", - 929: "Ρ", - 931: "Σ", - 932: "Τ", - 933: "Υ", - 934: "Φ", - 935: "Χ", - 936: "Ψ", - 937: "Ω", - 945: "α", - 946: "β", - 947: "γ", - 948: "δ", - 949: "ε", - 950: "ζ", - 951: "η", - 952: "θ", - 953: "ι", - 954: "κ", - 955: "λ", - 956: "μ", - 957: "ν", - 958: "ξ", - 959: "ο", - 960: "π", - 961: "ρ", - 962: "ς", - 963: "σ", - 964: "τ", - 965: "υ", - 966: "φ", - 967: "χ", - 968: "ψ", - 969: "ω", - 977: "ϑ", - 978: "ϒ", - 981: "ϕ", - 982: "ϖ", - 988: "Ϝ", - 989: "ϝ", - 1008: "ϰ", - 1009: "ϱ", - 1013: "ε", - 1014: "϶", - 1025: "Ё", - 1026: "Ђ", - 1027: "Ѓ", - 1028: "Є", - 1029: "Ѕ", - 1030: "І", - 1031: "Ї", - 1032: "Ј", - 1033: "Љ", - 1034: "Њ", - 1035: "Ћ", - 1036: "Ќ", - 1038: "Ў", - 1039: "Џ", - 1040: "А", - 1041: "Б", - 1042: "В", - 1043: "Г", - 1044: "Д", - 1045: "Е", - 1046: "Ж", - 1047: "З", - 1048: "И", - 1049: "Й", - 1050: "К", - 1051: "Л", - 1052: "М", - 1053: "Н", - 1054: "О", - 1055: "П", - 1056: "Р", - 1057: "С", - 1058: "Т", - 1059: "У", - 1060: "Ф", - 1061: "Х", - 1062: "Ц", - 1063: "Ч", - 1064: "Ш", - 1065: "Щ", - 1066: "Ъ", - 1067: "Ы", - 1068: "Ь", - 1069: "Э", - 1070: "Ю", - 1071: "Я", - 1072: "а", - 1073: "б", - 1074: "в", - 1075: "г", - 1076: "д", - 1077: "е", - 1078: "ж", - 1079: "з", - 1080: "и", - 1081: "й", - 1082: "к", - 1083: "л", - 1084: "м", - 1085: "н", - 1086: "о", - 1087: "п", - 1088: "р", - 1089: "с", - 1090: "т", - 1091: "у", - 1092: "ф", - 1093: "х", - 1094: "ц", - 1095: "ч", - 1096: "ш", - 1097: "щ", - 1098: "ъ", - 1099: "ы", - 1100: "ь", - 1101: "э", - 1102: "ю", - 1103: "я", - 1105: "ё", - 1106: "ђ", - 1107: "ѓ", - 1108: "є", - 1109: "ѕ", - 1110: "і", - 1111: "ї", - 1112: "ј", - 1113: "љ", - 1114: "њ", - 1115: "ћ", - 1116: "ќ", - 1118: "ў", - 1119: "џ", - 8194: " ", - 8195: " ", - 8196: " ", - 8197: " ", - 8199: " ", - 8200: " ", - 8201: " ", - 8202: " ", - 8203: "​", - 8204: "‌", - 8205: "‍", - 8206: "‎", - 8207: "‏", - 8208: "‐", - 8211: "–", - 8212: "—", - 8213: "―", - 8214: "‖", - 8216: "‘", - 8217: "’", - 8218: "‚", - 8220: "“", - 8221: "”", - 8222: "„", - 8224: "†", - 8225: "‡", - 8226: "•", - 8229: "‥", - 8230: "…", - 8240: "‰", - 8241: "‱", - 8242: "′", - 8243: "″", - 8244: "‴", - 8245: "‵", - 8249: "‹", - 8250: "›", - 8254: "‾", - 8257: "⁁", - 8259: "⁃", - 8260: "⁄", - 8271: "⁏", - 8279: "⁗", - 8287: " ", - 8288: "⁠", - 8289: "⁡", - 8290: "⁢", - 8291: "⁣", - 8364: "€", - 8411: "⃛", - 8412: "⃜", - 8450: "ℂ", - 8453: "℅", - 8458: "ℊ", - 8459: "ℋ", - 8460: "ℌ", - 8461: "ℍ", - 8462: "ℎ", - 8463: "ℏ", - 8464: "ℐ", - 8465: "ℑ", - 8466: "ℒ", - 8467: "ℓ", - 8469: "ℕ", - 8470: "№", - 8471: "℗", - 8472: "℘", - 8473: "ℙ", - 8474: "ℚ", - 8475: "ℛ", - 8476: "ℜ", - 8477: "ℝ", - 8478: "℞", - 8482: "™", - 8484: "ℤ", - 8486: "Ω", - 8487: "℧", - 8488: "ℨ", - 8489: "℩", - 8491: "Å", - 8492: "ℬ", - 8493: "ℭ", - 8495: "ℯ", - 8496: "ℰ", - 8497: "ℱ", - 8499: "ℳ", - 8500: "ℴ", - 8501: "ℵ", - 8502: "ℶ", - 8503: "ℷ", - 8504: "ℸ", - 8517: "ⅅ", - 8518: "ⅆ", - 8519: "ⅇ", - 8520: "ⅈ", - 8531: "⅓", - 8532: "⅔", - 8533: "⅕", - 8534: "⅖", - 8535: "⅗", - 8536: "⅘", - 8537: "⅙", - 8538: "⅚", - 8539: "⅛", - 8540: "⅜", - 8541: "⅝", - 8542: "⅞", - 8592: "←", - 8593: "↑", - 8594: "→", - 8595: "↓", - 8596: "↔", - 8597: "↕", - 8598: "↖", - 8599: "↗", - 8600: "↘", - 8601: "↙", - 8602: "↚", - 8603: "↛", - 8605: "↝", - 8606: "↞", - 8607: "↟", - 8608: "↠", - 8609: "↡", - 8610: "↢", - 8611: "↣", - 8612: "↤", - 8613: "↥", - 8614: "↦", - 8615: "↧", - 8617: "↩", - 8618: "↪", - 8619: "↫", - 8620: "↬", - 8621: "↭", - 8622: "↮", - 8624: "↰", - 8625: "↱", - 8626: "↲", - 8627: "↳", - 8629: "↵", - 8630: "↶", - 8631: "↷", - 8634: "↺", - 8635: "↻", - 8636: "↼", - 8637: "↽", - 8638: "↾", - 8639: "↿", - 8640: "⇀", - 8641: "⇁", - 8642: "⇂", - 8643: "⇃", - 8644: "⇄", - 8645: "⇅", - 8646: "⇆", - 8647: "⇇", - 8648: "⇈", - 8649: "⇉", - 8650: "⇊", - 8651: "⇋", - 8652: "⇌", - 8653: "⇍", - 8654: "⇎", - 8655: "⇏", - 8656: "⇐", - 8657: "⇑", - 8658: "⇒", - 8659: "⇓", - 8660: "⇔", - 8661: "⇕", - 8662: "⇖", - 8663: "⇗", - 8664: "⇘", - 8665: "⇙", - 8666: "⇚", - 8667: "⇛", - 8669: "⇝", - 8676: "⇤", - 8677: "⇥", - 8693: "⇵", - 8701: "⇽", - 8702: "⇾", - 8703: "⇿", - 8704: "∀", - 8705: "∁", - 8706: "∂", - 8707: "∃", - 8708: "∄", - 8709: "∅", - 8711: "∇", - 8712: "∈", - 8713: "∉", - 8715: "∋", - 8716: "∌", - 8719: "∏", - 8720: "∐", - 8721: "∑", - 8722: "−", - 8723: "∓", - 8724: "∔", - 8726: "∖", - 8727: "∗", - 8728: "∘", - 8730: "√", - 8733: "∝", - 8734: "∞", - 8735: "∟", - 8736: "∠", - 8737: "∡", - 8738: "∢", - 8739: "∣", - 8740: "∤", - 8741: "∥", - 8742: "∦", - 8743: "∧", - 8744: "∨", - 8745: "∩", - 8746: "∪", - 8747: "∫", - 8748: "∬", - 8749: "∭", - 8750: "∮", - 8751: "∯", - 8752: "∰", - 8753: "∱", - 8754: "∲", - 8755: "∳", - 8756: "∴", - 8757: "∵", - 8758: "∶", - 8759: "∷", - 8760: "∸", - 8762: "∺", - 8763: "∻", - 8764: "∼", - 8765: "∽", - 8766: "∾", - 8767: "∿", - 8768: "≀", - 8769: "≁", - 8770: "≂", - 8771: "≃", - 8772: "≄", - 8773: "≅", - 8774: "≆", - 8775: "≇", - 8776: "≈", - 8777: "≉", - 8778: "≊", - 8779: "≋", - 8780: "≌", - 8781: "≍", - 8782: "≎", - 8783: "≏", - 8784: "≐", - 8785: "≑", - 8786: "≒", - 8787: "≓", - 8788: "≔", - 8789: "≕", - 8790: "≖", - 8791: "≗", - 8793: "≙", - 8794: "≚", - 8796: "≜", - 8799: "≟", - 8800: "≠", - 8801: "≡", - 8802: "≢", - 8804: "≤", - 8805: "≥", - 8806: "≦", - 8807: "≧", - 8808: "≨", - 8809: "≩", - 8810: "≪", - 8811: "≫", - 8812: "≬", - 8813: "≭", - 8814: "≮", - 8815: "≯", - 8816: "≰", - 8817: "≱", - 8818: "≲", - 8819: "≳", - 8820: "≴", - 8821: "≵", - 8822: "≶", - 8823: "≷", - 8824: "≸", - 8825: "≹", - 8826: "≺", - 8827: "≻", - 8828: "≼", - 8829: "≽", - 8830: "≾", - 8831: "≿", - 8832: "⊀", - 8833: "⊁", - 8834: "⊂", - 8835: "⊃", - 8836: "⊄", - 8837: "⊅", - 8838: "⊆", - 8839: "⊇", - 8840: "⊈", - 8841: "⊉", - 8842: "⊊", - 8843: "⊋", - 8845: "⊍", - 8846: "⊎", - 8847: "⊏", - 8848: "⊐", - 8849: "⊑", - 8850: "⊒", - 8851: "⊓", - 8852: "⊔", - 8853: "⊕", - 8854: "⊖", - 8855: "⊗", - 8856: "⊘", - 8857: "⊙", - 8858: "⊚", - 8859: "⊛", - 8861: "⊝", - 8862: "⊞", - 8863: "⊟", - 8864: "⊠", - 8865: "⊡", - 8866: "⊢", - 8867: "⊣", - 8868: "⊤", - 8869: "⊥", - 8871: "⊧", - 8872: "⊨", - 8873: "⊩", - 8874: "⊪", - 8875: "⊫", - 8876: "⊬", - 8877: "⊭", - 8878: "⊮", - 8879: "⊯", - 8880: "⊰", - 8882: "⊲", - 8883: "⊳", - 8884: "⊴", - 8885: "⊵", - 8886: "⊶", - 8887: "⊷", - 8888: "⊸", - 8889: "⊹", - 8890: "⊺", - 8891: "⊻", - 8893: "⊽", - 8894: "⊾", - 8895: "⊿", - 8896: "⋀", - 8897: "⋁", - 8898: "⋂", - 8899: "⋃", - 8900: "⋄", - 8901: "⋅", - 8902: "⋆", - 8903: "⋇", - 8904: "⋈", - 8905: "⋉", - 8906: "⋊", - 8907: "⋋", - 8908: "⋌", - 8909: "⋍", - 8910: "⋎", - 8911: "⋏", - 8912: "⋐", - 8913: "⋑", - 8914: "⋒", - 8915: "⋓", - 8916: "⋔", - 8917: "⋕", - 8918: "⋖", - 8919: "⋗", - 8920: "⋘", - 8921: "⋙", - 8922: "⋚", - 8923: "⋛", - 8926: "⋞", - 8927: "⋟", - 8928: "⋠", - 8929: "⋡", - 8930: "⋢", - 8931: "⋣", - 8934: "⋦", - 8935: "⋧", - 8936: "⋨", - 8937: "⋩", - 8938: "⋪", - 8939: "⋫", - 8940: "⋬", - 8941: "⋭", - 8942: "⋮", - 8943: "⋯", - 8944: "⋰", - 8945: "⋱", - 8946: "⋲", - 8947: "⋳", - 8948: "⋴", - 8949: "⋵", - 8950: "⋶", - 8951: "⋷", - 8953: "⋹", - 8954: "⋺", - 8955: "⋻", - 8956: "⋼", - 8957: "⋽", - 8958: "⋾", - 8965: "⌅", - 8966: "⌆", - 8968: "⌈", - 8969: "⌉", - 8970: "⌊", - 8971: "⌋", - 8972: "⌌", - 8973: "⌍", - 8974: "⌎", - 8975: "⌏", - 8976: "⌐", - 8978: "⌒", - 8979: "⌓", - 8981: "⌕", - 8982: "⌖", - 8988: "⌜", - 8989: "⌝", - 8990: "⌞", - 8991: "⌟", - 8994: "⌢", - 8995: "⌣", - 9001: "⟨", - 9002: "⟩", - 9005: "⌭", - 9006: "⌮", - 9014: "⌶", - 9021: "⌽", - 9023: "⌿", - 9084: "⍼", - 9136: "⎰", - 9137: "⎱", - 9140: "⎴", - 9141: "⎵", - 9142: "⎶", - 9180: "⏜", - 9181: "⏝", - 9182: "⏞", - 9183: "⏟", - 9186: "⏢", - 9191: "⏧", - 9251: "␣", - 9416: "Ⓢ", - 9472: "─", - 9474: "│", - 9484: "┌", - 9488: "┐", - 9492: "└", - 9496: "┘", - 9500: "├", - 9508: "┤", - 9516: "┬", - 9524: "┴", - 9532: "┼", - 9552: "═", - 9553: "║", - 9554: "╒", - 9555: "╓", - 9556: "╔", - 9557: "╕", - 9558: "╖", - 9559: "╗", - 9560: "╘", - 9561: "╙", - 9562: "╚", - 9563: "╛", - 9564: "╜", - 9565: "╝", - 9566: "╞", - 9567: "╟", - 9568: "╠", - 9569: "╡", - 9570: "╢", - 9571: "╣", - 9572: "╤", - 9573: "╥", - 9674: "◊", - 9675: "○", - 9708: "◬", - 9711: "◯", - 9720: "◸", - 9721: "◹", - 9722: "◺", - 9723: "◻", - 9724: "◼", - 9733: "★", - 9734: "☆", - 9742: "☎", - 9792: "♀", - 9794: "♂", - 9824: "♠", - 9827: "♣", - 9829: "♥", - 9830: "♦", - 9834: "♪", - 9837: "♭", - 9838: "♮", - 9839: "♯", - 10003: "✓", - 10007: "✗", - 10016: "✠", - 10038: "✶", - 10072: "❘", - 10098: "❲", - 10099: "❳", - 10214: "⟦", - 10215: "⟧", - 10216: "⟨", - 10217: "⟩", - 10218: "⟪", - 10219: "⟫", - 10220: "⟬", - 10221: "⟭", - 10229: "⟵", - 10230: "⟶", - 10231: "⟷", - 10232: "⟸", - 10233: "⟹", - 10234: "⟺", - 10236: "⟼", - 10239: "⟿", - 10498: "⤂", - 10499: "⤃", - 10500: "⤄", - 10501: "⤅", - 10508: "⤌", - 10509: "⤍", - 10510: "⤎", - 10511: "⤏", - 10512: "⤐", - 10513: "⤑", - 10514: "⤒", - 10515: "⤓", - 10518: "⤖", - 10521: "⤙", - 10522: "⤚", - 10523: "⤛", - 10524: "⤜", - 10525: "⤝", - 10526: "⤞", - 10527: "⤟", - 10528: "⤠", - 10531: "⤣", - 10532: "⤤", - 10533: "⤥", - 10534: "⤦", - 10535: "⤧", - 10536: "⤨", - 10537: "⤩", - 10538: "⤪", - 10547: "⤳", - 10549: "⤵", - 10550: "⤶", - 10551: "⤷", - 10552: "⤸", - 10553: "⤹", - 10556: "⤼", - 10557: "⤽", - 10565: "⥅", - 10568: "⥈", - 10569: "⥉", - 10570: "⥊", - 10571: "⥋", - 10574: "⥎", - 10575: "⥏", - 10576: "⥐", - 10577: "⥑", - 10578: "⥒", - 10579: "⥓", - 10580: "⥔", - 10581: "⥕", - 10582: "⥖", - 10583: "⥗", - 10584: "⥘", - 10585: "⥙", - 10586: "⥚", - 10587: "⥛", - 10588: "⥜", - 10589: "⥝", - 10590: "⥞", - 10591: "⥟", - 10592: "⥠", - 10593: "⥡", - 10594: "⥢", - 10595: "⥣", - 10596: "⥤", - 10597: "⥥", - 10598: "⥦", - 10599: "⥧", - 10600: "⥨", - 10601: "⥩", - 10602: "⥪", - 10603: "⥫", - 10604: "⥬", - 10605: "⥭", - 10606: "⥮", - 10607: "⥯", - 10608: "⥰", - 10609: "⥱", - 10610: "⥲", - 10611: "⥳", - 10612: "⥴", - 10613: "⥵", - 10614: "⥶", - 10616: "⥸", - 10617: "⥹", - 10619: "⥻", - 10620: "⥼", - 10621: "⥽", - 10622: "⥾", - 10623: "⥿", - 10629: "⦅", - 10630: "⦆", - 10635: "⦋", - 10636: "⦌", - 10637: "⦍", - 10638: "⦎", - 10639: "⦏", - 10640: "⦐", - 10641: "⦑", - 10642: "⦒", - 10643: "⦓", - 10644: "⦔", - 10645: "⦕", - 10646: "⦖", - 10650: "⦚", - 10652: "⦜", - 10653: "⦝", - 10660: "⦤", - 10661: "⦥", - 10662: "⦦", - 10663: "⦧", - 10664: "⦨", - 10665: "⦩", - 10666: "⦪", - 10667: "⦫", - 10668: "⦬", - 10669: "⦭", - 10670: "⦮", - 10671: "⦯", - 10672: "⦰", - 10673: "⦱", - 10674: "⦲", - 10675: "⦳", - 10676: "⦴", - 10677: "⦵", - 10678: "⦶", - 10679: "⦷", - 10681: "⦹", - 10683: "⦻", - 10684: "⦼", - 10686: "⦾", - 10687: "⦿", - 10688: "⧀", - 10689: "⧁", - 10690: "⧂", - 10691: "⧃", - 10692: "⧄", - 10693: "⧅", - 10697: "⧉", - 10701: "⧍", - 10702: "⧎", - 10703: "⧏", - 10704: "⧐", - 10714: "∽̱", - 10716: "⧜", - 10717: "⧝", - 10718: "⧞", - 10723: "⧣", - 10724: "⧤", - 10725: "⧥", - 10731: "⧫", - 10740: "⧴", - 10742: "⧶", - 10752: "⨀", - 10753: "⨁", - 10754: "⨂", - 10756: "⨄", - 10758: "⨆", - 10764: "⨌", - 10765: "⨍", - 10768: "⨐", - 10769: "⨑", - 10770: "⨒", - 10771: "⨓", - 10772: "⨔", - 10773: "⨕", - 10774: "⨖", - 10775: "⨗", - 10786: "⨢", - 10787: "⨣", - 10788: "⨤", - 10789: "⨥", - 10790: "⨦", - 10791: "⨧", - 10793: "⨩", - 10794: "⨪", - 10797: "⨭", - 10798: "⨮", - 10799: "⨯", - 10800: "⨰", - 10801: "⨱", - 10803: "⨳", - 10804: "⨴", - 10805: "⨵", - 10806: "⨶", - 10807: "⨷", - 10808: "⨸", - 10809: "⨹", - 10810: "⨺", - 10811: "⨻", - 10812: "⨼", - 10815: "⨿", - 10816: "⩀", - 10818: "⩂", - 10819: "⩃", - 10820: "⩄", - 10821: "⩅", - 10822: "⩆", - 10823: "⩇", - 10824: "⩈", - 10825: "⩉", - 10826: "⩊", - 10827: "⩋", - 10828: "⩌", - 10829: "⩍", - 10832: "⩐", - 10835: "⩓", - 10836: "⩔", - 10837: "⩕", - 10838: "⩖", - 10839: "⩗", - 10840: "⩘", - 10842: "⩚", - 10843: "⩛", - 10844: "⩜", - 10845: "⩝", - 10847: "⩟", - 10854: "⩦", - 10858: "⩪", - 10861: "⩭", - 10862: "⩮", - 10863: "⩯", - 10864: "⩰", - 10865: "⩱", - 10866: "⩲", - 10867: "⩳", - 10868: "⩴", - 10869: "⩵", - 10871: "⩷", - 10872: "⩸", - 10873: "⩹", - 10874: "⩺", - 10875: "⩻", - 10876: "⩼", - 10877: "⩽", - 10878: "⩾", - 10879: "⩿", - 10880: "⪀", - 10881: "⪁", - 10882: "⪂", - 10883: "⪃", - 10884: "⪄", - 10885: "⪅", - 10886: "⪆", - 10887: "⪇", - 10888: "⪈", - 10889: "⪉", - 10890: "⪊", - 10891: "⪋", - 10892: "⪌", - 10893: "⪍", - 10894: "⪎", - 10895: "⪏", - 10896: "⪐", - 10897: "⪑", - 10898: "⪒", - 10899: "⪓", - 10900: "⪔", - 10901: "⪕", - 10902: "⪖", - 10903: "⪗", - 10904: "⪘", - 10905: "⪙", - 10906: "⪚", - 10909: "⪝", - 10910: "⪞", - 10911: "⪟", - 10912: "⪠", - 10913: "⪡", - 10914: "⪢", - 10916: "⪤", - 10917: "⪥", - 10918: "⪦", - 10919: "⪧", - 10920: "⪨", - 10921: "⪩", - 10922: "⪪", - 10923: "⪫", - 10924: "⪬", - 10925: "⪭", - 10926: "⪮", - 10927: "⪯", - 10928: "⪰", - 10931: "⪳", - 10932: "⪴", - 10933: "⪵", - 10934: "⪶", - 10935: "⪷", - 10936: "⪸", - 10937: "⪹", - 10938: "⪺", - 10939: "⪻", - 10940: "⪼", - 10941: "⪽", - 10942: "⪾", - 10943: "⪿", - 10944: "⫀", - 10945: "⫁", - 10946: "⫂", - 10947: "⫃", - 10948: "⫄", - 10949: "⫅", - 10950: "⫆", - 10951: "⫇", - 10952: "⫈", - 10955: "⫋", - 10956: "⫌", - 10959: "⫏", - 10960: "⫐", - 10961: "⫑", - 10962: "⫒", - 10963: "⫓", - 10964: "⫔", - 10965: "⫕", - 10966: "⫖", - 10967: "⫗", - 10968: "⫘", - 10969: "⫙", - 10970: "⫚", - 10971: "⫛", - 10980: "⫤", - 10982: "⫦", - 10983: "⫧", - 10984: "⫨", - 10985: "⫩", - 10987: "⫫", - 10988: "⫬", - 10989: "⫭", - 10990: "⫮", - 10991: "⫯", - 10992: "⫰", - 10993: "⫱", - 10994: "⫲", - 10995: "⫳", - 11005: "⫽", - 64256: "ff", - 64257: "fi", - 64258: "fl", - 64259: "ffi", - 64260: "ffl", - 119964: "𝒜", - 119966: "𝒞", - 119967: "𝒟", - 119970: "𝒢", - 119973: "𝒥", - 119974: "𝒦", - 119977: "𝒩", - 119978: "𝒪", - 119979: "𝒫", - 119980: "𝒬", - 119982: "𝒮", - 119983: "𝒯", - 119984: "𝒰", - 119985: "𝒱", - 119986: "𝒲", - 119987: "𝒳", - 119988: "𝒴", - 119989: "𝒵", - 119990: "𝒶", - 119991: "𝒷", - 119992: "𝒸", - 119993: "𝒹", - 119995: "𝒻", - 119997: "𝒽", - 119998: "𝒾", - 119999: "𝒿", - 120000: "𝓀", - 120001: "𝓁", - 120002: "𝓂", - 120003: "𝓃", - 120005: "𝓅", - 120006: "𝓆", - 120007: "𝓇", - 120008: "𝓈", - 120009: "𝓉", - 120010: "𝓊", - 120011: "𝓋", - 120012: "𝓌", - 120013: "𝓍", - 120014: "𝓎", - 120015: "𝓏", - 120068: "𝔄", - 120069: "𝔅", - 120071: "𝔇", - 120072: "𝔈", - 120073: "𝔉", - 120074: "𝔊", - 120077: "𝔍", - 120078: "𝔎", - 120079: "𝔏", - 120080: "𝔐", - 120081: "𝔑", - 120082: "𝔒", - 120083: "𝔓", - 120084: "𝔔", - 120086: "𝔖", - 120087: "𝔗", - 120088: "𝔘", - 120089: "𝔙", - 120090: "𝔚", - 120091: "𝔛", - 120092: "𝔜", - 120094: "𝔞", - 120095: "𝔟", - 120096: "𝔠", - 120097: "𝔡", - 120098: "𝔢", - 120099: "𝔣", - 120100: "𝔤", - 120101: "𝔥", - 120102: "𝔦", - 120103: "𝔧", - 120104: "𝔨", - 120105: "𝔩", - 120106: "𝔪", - 120107: "𝔫", - 120108: "𝔬", - 120109: "𝔭", - 120110: "𝔮", - 120111: "𝔯", - 120112: "𝔰", - 120113: "𝔱", - 120114: "𝔲", - 120115: "𝔳", - 120116: "𝔴", - 120117: "𝔵", - 120118: "𝔶", - 120119: "𝔷", - 120120: "𝔸", - 120121: "𝔹", - 120123: "𝔻", - 120124: "𝔼", - 120125: "𝔽", - 120126: "𝔾", - 120128: "𝕀", - 120129: "𝕁", - 120130: "𝕂", - 120131: "𝕃", - 120132: "𝕄", - 120134: "𝕆", - 120138: "𝕊", - 120139: "𝕋", - 120140: "𝕌", - 120141: "𝕍", - 120142: "𝕎", - 120143: "𝕏", - 120144: "𝕐", - 120146: "𝕒", - 120147: "𝕓", - 120148: "𝕔", - 120149: "𝕕", - 120150: "𝕖", - 120151: "𝕗", - 120152: "𝕘", - 120153: "𝕙", - 120154: "𝕚", - 120155: "𝕛", - 120156: "𝕜", - 120157: "𝕝", - 120158: "𝕞", - 120159: "𝕟", - 120160: "𝕠", - 120161: "𝕡", - 120162: "𝕢", - 120163: "𝕣", - 120164: "𝕤", - 120165: "𝕥", - 120166: "𝕦", - 120167: "𝕧", - 120168: "𝕨", - 120169: "𝕩", - 120170: "𝕪", - 120171: "𝕫" -}; - export default ToHTMLEntity; diff --git a/src/core/vendor/htmlEntities/entity.json b/src/core/vendor/htmlEntities/entity.json new file mode 100644 index 00000000..557170b4 --- /dev/null +++ b/src/core/vendor/htmlEntities/entity.json @@ -0,0 +1,2233 @@ +{ + "Æ": { "codepoints": [198], "characters": "\u00C6" }, + "Æ": { "codepoints": [198], "characters": "\u00C6" }, + "&": { "codepoints": [38], "characters": "\u0026" }, + "&": { "codepoints": [38], "characters": "\u0026" }, + "Á": { "codepoints": [193], "characters": "\u00C1" }, + "Á": { "codepoints": [193], "characters": "\u00C1" }, + "Ă": { "codepoints": [258], "characters": "\u0102" }, + "Â": { "codepoints": [194], "characters": "\u00C2" }, + "Â": { "codepoints": [194], "characters": "\u00C2" }, + "А": { "codepoints": [1040], "characters": "\u0410" }, + "𝔄": { "codepoints": [120068], "characters": "\uD835\uDD04" }, + "À": { "codepoints": [192], "characters": "\u00C0" }, + "À": { "codepoints": [192], "characters": "\u00C0" }, + "Α": { "codepoints": [913], "characters": "\u0391" }, + "Ā": { "codepoints": [256], "characters": "\u0100" }, + "⩓": { "codepoints": [10835], "characters": "\u2A53" }, + "Ą": { "codepoints": [260], "characters": "\u0104" }, + "𝔸": { "codepoints": [120120], "characters": "\uD835\uDD38" }, + "⁡": { "codepoints": [8289], "characters": "\u2061" }, + "Å": { "codepoints": [197], "characters": "\u00C5" }, + "Å": { "codepoints": [197], "characters": "\u00C5" }, + "𝒜": { "codepoints": [119964], "characters": "\uD835\uDC9C" }, + "≔": { "codepoints": [8788], "characters": "\u2254" }, + "Ã": { "codepoints": [195], "characters": "\u00C3" }, + "Ã": { "codepoints": [195], "characters": "\u00C3" }, + "Ä": { "codepoints": [196], "characters": "\u00C4" }, + "Ä": { "codepoints": [196], "characters": "\u00C4" }, + "∖": { "codepoints": [8726], "characters": "\u2216" }, + "⫧": { "codepoints": [10983], "characters": "\u2AE7" }, + "⌆": { "codepoints": [8966], "characters": "\u2306" }, + "Б": { "codepoints": [1041], "characters": "\u0411" }, + "∵": { "codepoints": [8757], "characters": "\u2235" }, + "ℬ": { "codepoints": [8492], "characters": "\u212C" }, + "Β": { "codepoints": [914], "characters": "\u0392" }, + "𝔅": { "codepoints": [120069], "characters": "\uD835\uDD05" }, + "𝔹": { "codepoints": [120121], "characters": "\uD835\uDD39" }, + "˘": { "codepoints": [728], "characters": "\u02D8" }, + "ℬ": { "codepoints": [8492], "characters": "\u212C" }, + "≎": { "codepoints": [8782], "characters": "\u224E" }, + "Ч": { "codepoints": [1063], "characters": "\u0427" }, + "©": { "codepoints": [169], "characters": "\u00A9" }, + "©": { "codepoints": [169], "characters": "\u00A9" }, + "Ć": { "codepoints": [262], "characters": "\u0106" }, + "⋒": { "codepoints": [8914], "characters": "\u22D2" }, + "ⅅ": { "codepoints": [8517], "characters": "\u2145" }, + "ℭ": { "codepoints": [8493], "characters": "\u212D" }, + "Č": { "codepoints": [268], "characters": "\u010C" }, + "Ç": { "codepoints": [199], "characters": "\u00C7" }, + "Ç": { "codepoints": [199], "characters": "\u00C7" }, + "Ĉ": { "codepoints": [264], "characters": "\u0108" }, + "∰": { "codepoints": [8752], "characters": "\u2230" }, + "Ċ": { "codepoints": [266], "characters": "\u010A" }, + "¸": { "codepoints": [184], "characters": "\u00B8" }, + "·": { "codepoints": [183], "characters": "\u00B7" }, + "ℭ": { "codepoints": [8493], "characters": "\u212D" }, + "Χ": { "codepoints": [935], "characters": "\u03A7" }, + "⊙": { "codepoints": [8857], "characters": "\u2299" }, + "⊖": { "codepoints": [8854], "characters": "\u2296" }, + "⊕": { "codepoints": [8853], "characters": "\u2295" }, + "⊗": { "codepoints": [8855], "characters": "\u2297" }, + "∲": { "codepoints": [8754], "characters": "\u2232" }, + "”": { "codepoints": [8221], "characters": "\u201D" }, + "’": { "codepoints": [8217], "characters": "\u2019" }, + "∷": { "codepoints": [8759], "characters": "\u2237" }, + "⩴": { "codepoints": [10868], "characters": "\u2A74" }, + "≡": { "codepoints": [8801], "characters": "\u2261" }, + "∯": { "codepoints": [8751], "characters": "\u222F" }, + "∮": { "codepoints": [8750], "characters": "\u222E" }, + "ℂ": { "codepoints": [8450], "characters": "\u2102" }, + "∐": { "codepoints": [8720], "characters": "\u2210" }, + "∳": { "codepoints": [8755], "characters": "\u2233" }, + "⨯": { "codepoints": [10799], "characters": "\u2A2F" }, + "𝒞": { "codepoints": [119966], "characters": "\uD835\uDC9E" }, + "⋓": { "codepoints": [8915], "characters": "\u22D3" }, + "≍": { "codepoints": [8781], "characters": "\u224D" }, + "ⅅ": { "codepoints": [8517], "characters": "\u2145" }, + "⤑": { "codepoints": [10513], "characters": "\u2911" }, + "Ђ": { "codepoints": [1026], "characters": "\u0402" }, + "Ѕ": { "codepoints": [1029], "characters": "\u0405" }, + "Џ": { "codepoints": [1039], "characters": "\u040F" }, + "‡": { "codepoints": [8225], "characters": "\u2021" }, + "↡": { "codepoints": [8609], "characters": "\u21A1" }, + "⫤": { "codepoints": [10980], "characters": "\u2AE4" }, + "Ď": { "codepoints": [270], "characters": "\u010E" }, + "Д": { "codepoints": [1044], "characters": "\u0414" }, + "∇": { "codepoints": [8711], "characters": "\u2207" }, + "Δ": { "codepoints": [916], "characters": "\u0394" }, + "𝔇": { "codepoints": [120071], "characters": "\uD835\uDD07" }, + "´": { "codepoints": [180], "characters": "\u00B4" }, + "˙": { "codepoints": [729], "characters": "\u02D9" }, + "˝": { "codepoints": [733], "characters": "\u02DD" }, + "`": { "codepoints": [96], "characters": "\u0060" }, + "˜": { "codepoints": [732], "characters": "\u02DC" }, + "⋄": { "codepoints": [8900], "characters": "\u22C4" }, + "ⅆ": { "codepoints": [8518], "characters": "\u2146" }, + "𝔻": { "codepoints": [120123], "characters": "\uD835\uDD3B" }, + "¨": { "codepoints": [168], "characters": "\u00A8" }, + "⃜": { "codepoints": [8412], "characters": "\u20DC" }, + "≐": { "codepoints": [8784], "characters": "\u2250" }, + "∯": { "codepoints": [8751], "characters": "\u222F" }, + "¨": { "codepoints": [168], "characters": "\u00A8" }, + "⇓": { "codepoints": [8659], "characters": "\u21D3" }, + "⇐": { "codepoints": [8656], "characters": "\u21D0" }, + "⇔": { "codepoints": [8660], "characters": "\u21D4" }, + "⫤": { "codepoints": [10980], "characters": "\u2AE4" }, + "⟸": { "codepoints": [10232], "characters": "\u27F8" }, + "⟺": { "codepoints": [10234], "characters": "\u27FA" }, + "⟹": { "codepoints": [10233], "characters": "\u27F9" }, + "⇒": { "codepoints": [8658], "characters": "\u21D2" }, + "⊨": { "codepoints": [8872], "characters": "\u22A8" }, + "⇑": { "codepoints": [8657], "characters": "\u21D1" }, + "⇕": { "codepoints": [8661], "characters": "\u21D5" }, + "∥": { "codepoints": [8741], "characters": "\u2225" }, + "↓": { "codepoints": [8595], "characters": "\u2193" }, + "⤓": { "codepoints": [10515], "characters": "\u2913" }, + "⇵": { "codepoints": [8693], "characters": "\u21F5" }, + "̑": { "codepoints": [785], "characters": "\u0311" }, + "⥐": { "codepoints": [10576], "characters": "\u2950" }, + "⥞": { "codepoints": [10590], "characters": "\u295E" }, + "↽": { "codepoints": [8637], "characters": "\u21BD" }, + "⥖": { "codepoints": [10582], "characters": "\u2956" }, + "⥟": { "codepoints": [10591], "characters": "\u295F" }, + "⇁": { "codepoints": [8641], "characters": "\u21C1" }, + "⥗": { "codepoints": [10583], "characters": "\u2957" }, + "⊤": { "codepoints": [8868], "characters": "\u22A4" }, + "↧": { "codepoints": [8615], "characters": "\u21A7" }, + "⇓": { "codepoints": [8659], "characters": "\u21D3" }, + "𝒟": { "codepoints": [119967], "characters": "\uD835\uDC9F" }, + "Đ": { "codepoints": [272], "characters": "\u0110" }, + "Ŋ": { "codepoints": [330], "characters": "\u014A" }, + "Ð": { "codepoints": [208], "characters": "\u00D0" }, + "Ð": { "codepoints": [208], "characters": "\u00D0" }, + "É": { "codepoints": [201], "characters": "\u00C9" }, + "É": { "codepoints": [201], "characters": "\u00C9" }, + "Ě": { "codepoints": [282], "characters": "\u011A" }, + "Ê": { "codepoints": [202], "characters": "\u00CA" }, + "Ê": { "codepoints": [202], "characters": "\u00CA" }, + "Э": { "codepoints": [1069], "characters": "\u042D" }, + "Ė": { "codepoints": [278], "characters": "\u0116" }, + "𝔈": { "codepoints": [120072], "characters": "\uD835\uDD08" }, + "È": { "codepoints": [200], "characters": "\u00C8" }, + "È": { "codepoints": [200], "characters": "\u00C8" }, + "∈": { "codepoints": [8712], "characters": "\u2208" }, + "Ē": { "codepoints": [274], "characters": "\u0112" }, + "◻": { "codepoints": [9723], "characters": "\u25FB" }, + "▫": { "codepoints": [9643], "characters": "\u25AB" }, + "Ę": { "codepoints": [280], "characters": "\u0118" }, + "𝔼": { "codepoints": [120124], "characters": "\uD835\uDD3C" }, + "Ε": { "codepoints": [917], "characters": "\u0395" }, + "⩵": { "codepoints": [10869], "characters": "\u2A75" }, + "≂": { "codepoints": [8770], "characters": "\u2242" }, + "⇌": { "codepoints": [8652], "characters": "\u21CC" }, + "ℰ": { "codepoints": [8496], "characters": "\u2130" }, + "⩳": { "codepoints": [10867], "characters": "\u2A73" }, + "Η": { "codepoints": [919], "characters": "\u0397" }, + "Ë": { "codepoints": [203], "characters": "\u00CB" }, + "Ë": { "codepoints": [203], "characters": "\u00CB" }, + "∃": { "codepoints": [8707], "characters": "\u2203" }, + "ⅇ": { "codepoints": [8519], "characters": "\u2147" }, + "Ф": { "codepoints": [1060], "characters": "\u0424" }, + "𝔉": { "codepoints": [120073], "characters": "\uD835\uDD09" }, + "◼": { "codepoints": [9724], "characters": "\u25FC" }, + "▪": { "codepoints": [9642], "characters": "\u25AA" }, + "𝔽": { "codepoints": [120125], "characters": "\uD835\uDD3D" }, + "∀": { "codepoints": [8704], "characters": "\u2200" }, + "ℱ": { "codepoints": [8497], "characters": "\u2131" }, + "ℱ": { "codepoints": [8497], "characters": "\u2131" }, + "Ѓ": { "codepoints": [1027], "characters": "\u0403" }, + ">": { "codepoints": [62], "characters": "\u003E" }, + ">": { "codepoints": [62], "characters": "\u003E" }, + "Γ": { "codepoints": [915], "characters": "\u0393" }, + "Ϝ": { "codepoints": [988], "characters": "\u03DC" }, + "Ğ": { "codepoints": [286], "characters": "\u011E" }, + "Ģ": { "codepoints": [290], "characters": "\u0122" }, + "Ĝ": { "codepoints": [284], "characters": "\u011C" }, + "Г": { "codepoints": [1043], "characters": "\u0413" }, + "Ġ": { "codepoints": [288], "characters": "\u0120" }, + "𝔊": { "codepoints": [120074], "characters": "\uD835\uDD0A" }, + "⋙": { "codepoints": [8921], "characters": "\u22D9" }, + "𝔾": { "codepoints": [120126], "characters": "\uD835\uDD3E" }, + "≥": { "codepoints": [8805], "characters": "\u2265" }, + "⋛": { "codepoints": [8923], "characters": "\u22DB" }, + "≧": { "codepoints": [8807], "characters": "\u2267" }, + "⪢": { "codepoints": [10914], "characters": "\u2AA2" }, + "≷": { "codepoints": [8823], "characters": "\u2277" }, + "⩾": { "codepoints": [10878], "characters": "\u2A7E" }, + "≳": { "codepoints": [8819], "characters": "\u2273" }, + "𝒢": { "codepoints": [119970], "characters": "\uD835\uDCA2" }, + "≫": { "codepoints": [8811], "characters": "\u226B" }, + "Ъ": { "codepoints": [1066], "characters": "\u042A" }, + "ˇ": { "codepoints": [711], "characters": "\u02C7" }, + "^": { "codepoints": [94], "characters": "\u005E" }, + "Ĥ": { "codepoints": [292], "characters": "\u0124" }, + "ℌ": { "codepoints": [8460], "characters": "\u210C" }, + "ℋ": { "codepoints": [8459], "characters": "\u210B" }, + "ℍ": { "codepoints": [8461], "characters": "\u210D" }, + "─": { "codepoints": [9472], "characters": "\u2500" }, + "ℋ": { "codepoints": [8459], "characters": "\u210B" }, + "Ħ": { "codepoints": [294], "characters": "\u0126" }, + "≎": { "codepoints": [8782], "characters": "\u224E" }, + "≏": { "codepoints": [8783], "characters": "\u224F" }, + "Е": { "codepoints": [1045], "characters": "\u0415" }, + "IJ": { "codepoints": [306], "characters": "\u0132" }, + "Ё": { "codepoints": [1025], "characters": "\u0401" }, + "Í": { "codepoints": [205], "characters": "\u00CD" }, + "Í": { "codepoints": [205], "characters": "\u00CD" }, + "Î": { "codepoints": [206], "characters": "\u00CE" }, + "Î": { "codepoints": [206], "characters": "\u00CE" }, + "И": { "codepoints": [1048], "characters": "\u0418" }, + "İ": { "codepoints": [304], "characters": "\u0130" }, + "ℑ": { "codepoints": [8465], "characters": "\u2111" }, + "Ì": { "codepoints": [204], "characters": "\u00CC" }, + "Ì": { "codepoints": [204], "characters": "\u00CC" }, + "ℑ": { "codepoints": [8465], "characters": "\u2111" }, + "Ī": { "codepoints": [298], "characters": "\u012A" }, + "ⅈ": { "codepoints": [8520], "characters": "\u2148" }, + "⇒": { "codepoints": [8658], "characters": "\u21D2" }, + "∬": { "codepoints": [8748], "characters": "\u222C" }, + "∫": { "codepoints": [8747], "characters": "\u222B" }, + "⋂": { "codepoints": [8898], "characters": "\u22C2" }, + "⁣": { "codepoints": [8291], "characters": "\u2063" }, + "⁢": { "codepoints": [8290], "characters": "\u2062" }, + "Į": { "codepoints": [302], "characters": "\u012E" }, + "𝕀": { "codepoints": [120128], "characters": "\uD835\uDD40" }, + "Ι": { "codepoints": [921], "characters": "\u0399" }, + "ℐ": { "codepoints": [8464], "characters": "\u2110" }, + "Ĩ": { "codepoints": [296], "characters": "\u0128" }, + "І": { "codepoints": [1030], "characters": "\u0406" }, + "Ï": { "codepoints": [207], "characters": "\u00CF" }, + "Ï": { "codepoints": [207], "characters": "\u00CF" }, + "Ĵ": { "codepoints": [308], "characters": "\u0134" }, + "Й": { "codepoints": [1049], "characters": "\u0419" }, + "𝔍": { "codepoints": [120077], "characters": "\uD835\uDD0D" }, + "𝕁": { "codepoints": [120129], "characters": "\uD835\uDD41" }, + "𝒥": { "codepoints": [119973], "characters": "\uD835\uDCA5" }, + "Ј": { "codepoints": [1032], "characters": "\u0408" }, + "Є": { "codepoints": [1028], "characters": "\u0404" }, + "Х": { "codepoints": [1061], "characters": "\u0425" }, + "Ќ": { "codepoints": [1036], "characters": "\u040C" }, + "Κ": { "codepoints": [922], "characters": "\u039A" }, + "Ķ": { "codepoints": [310], "characters": "\u0136" }, + "К": { "codepoints": [1050], "characters": "\u041A" }, + "𝔎": { "codepoints": [120078], "characters": "\uD835\uDD0E" }, + "𝕂": { "codepoints": [120130], "characters": "\uD835\uDD42" }, + "𝒦": { "codepoints": [119974], "characters": "\uD835\uDCA6" }, + "Љ": { "codepoints": [1033], "characters": "\u0409" }, + "<": { "codepoints": [60], "characters": "\u003C" }, + "<": { "codepoints": [60], "characters": "\u003C" }, + "Ĺ": { "codepoints": [313], "characters": "\u0139" }, + "Λ": { "codepoints": [923], "characters": "\u039B" }, + "⟪": { "codepoints": [10218], "characters": "\u27EA" }, + "ℒ": { "codepoints": [8466], "characters": "\u2112" }, + "↞": { "codepoints": [8606], "characters": "\u219E" }, + "Ľ": { "codepoints": [317], "characters": "\u013D" }, + "Ļ": { "codepoints": [315], "characters": "\u013B" }, + "Л": { "codepoints": [1051], "characters": "\u041B" }, + "⟨": { "codepoints": [10216], "characters": "\u27E8" }, + "←": { "codepoints": [8592], "characters": "\u2190" }, + "⇤": { "codepoints": [8676], "characters": "\u21E4" }, + "⇆": { "codepoints": [8646], "characters": "\u21C6" }, + "⌈": { "codepoints": [8968], "characters": "\u2308" }, + "⟦": { "codepoints": [10214], "characters": "\u27E6" }, + "⥡": { "codepoints": [10593], "characters": "\u2961" }, + "⇃": { "codepoints": [8643], "characters": "\u21C3" }, + "⥙": { "codepoints": [10585], "characters": "\u2959" }, + "⌊": { "codepoints": [8970], "characters": "\u230A" }, + "↔": { "codepoints": [8596], "characters": "\u2194" }, + "⥎": { "codepoints": [10574], "characters": "\u294E" }, + "⊣": { "codepoints": [8867], "characters": "\u22A3" }, + "↤": { "codepoints": [8612], "characters": "\u21A4" }, + "⥚": { "codepoints": [10586], "characters": "\u295A" }, + "⊲": { "codepoints": [8882], "characters": "\u22B2" }, + "⧏": { "codepoints": [10703], "characters": "\u29CF" }, + "⊴": { "codepoints": [8884], "characters": "\u22B4" }, + "⥑": { "codepoints": [10577], "characters": "\u2951" }, + "⥠": { "codepoints": [10592], "characters": "\u2960" }, + "↿": { "codepoints": [8639], "characters": "\u21BF" }, + "⥘": { "codepoints": [10584], "characters": "\u2958" }, + "↼": { "codepoints": [8636], "characters": "\u21BC" }, + "⥒": { "codepoints": [10578], "characters": "\u2952" }, + "⇐": { "codepoints": [8656], "characters": "\u21D0" }, + "⇔": { "codepoints": [8660], "characters": "\u21D4" }, + "⋚": { "codepoints": [8922], "characters": "\u22DA" }, + "≦": { "codepoints": [8806], "characters": "\u2266" }, + "≶": { "codepoints": [8822], "characters": "\u2276" }, + "⪡": { "codepoints": [10913], "characters": "\u2AA1" }, + "⩽": { "codepoints": [10877], "characters": "\u2A7D" }, + "≲": { "codepoints": [8818], "characters": "\u2272" }, + "𝔏": { "codepoints": [120079], "characters": "\uD835\uDD0F" }, + "⋘": { "codepoints": [8920], "characters": "\u22D8" }, + "⇚": { "codepoints": [8666], "characters": "\u21DA" }, + "Ŀ": { "codepoints": [319], "characters": "\u013F" }, + "⟵": { "codepoints": [10229], "characters": "\u27F5" }, + "⟷": { "codepoints": [10231], "characters": "\u27F7" }, + "⟶": { "codepoints": [10230], "characters": "\u27F6" }, + "⟸": { "codepoints": [10232], "characters": "\u27F8" }, + "⟺": { "codepoints": [10234], "characters": "\u27FA" }, + "⟹": { "codepoints": [10233], "characters": "\u27F9" }, + "𝕃": { "codepoints": [120131], "characters": "\uD835\uDD43" }, + "↙": { "codepoints": [8601], "characters": "\u2199" }, + "↘": { "codepoints": [8600], "characters": "\u2198" }, + "ℒ": { "codepoints": [8466], "characters": "\u2112" }, + "↰": { "codepoints": [8624], "characters": "\u21B0" }, + "Ł": { "codepoints": [321], "characters": "\u0141" }, + "≪": { "codepoints": [8810], "characters": "\u226A" }, + "⤅": { "codepoints": [10501], "characters": "\u2905" }, + "М": { "codepoints": [1052], "characters": "\u041C" }, + " ": { "codepoints": [8287], "characters": "\u205F" }, + "ℳ": { "codepoints": [8499], "characters": "\u2133" }, + "𝔐": { "codepoints": [120080], "characters": "\uD835\uDD10" }, + "∓": { "codepoints": [8723], "characters": "\u2213" }, + "𝕄": { "codepoints": [120132], "characters": "\uD835\uDD44" }, + "ℳ": { "codepoints": [8499], "characters": "\u2133" }, + "Μ": { "codepoints": [924], "characters": "\u039C" }, + "Њ": { "codepoints": [1034], "characters": "\u040A" }, + "Ń": { "codepoints": [323], "characters": "\u0143" }, + "Ň": { "codepoints": [327], "characters": "\u0147" }, + "Ņ": { "codepoints": [325], "characters": "\u0145" }, + "Н": { "codepoints": [1053], "characters": "\u041D" }, + "​": { "codepoints": [8203], "characters": "\u200B" }, + "​": { "codepoints": [8203], "characters": "\u200B" }, + "​": { "codepoints": [8203], "characters": "\u200B" }, + "​": { "codepoints": [8203], "characters": "\u200B" }, + "≫": { "codepoints": [8811], "characters": "\u226B" }, + "≪": { "codepoints": [8810], "characters": "\u226A" }, + " ": { "codepoints": [10], "characters": "\u000A" }, + "𝔑": { "codepoints": [120081], "characters": "\uD835\uDD11" }, + "⁠": { "codepoints": [8288], "characters": "\u2060" }, + " ": { "codepoints": [160], "characters": "\u00A0" }, + "ℕ": { "codepoints": [8469], "characters": "\u2115" }, + "⫬": { "codepoints": [10988], "characters": "\u2AEC" }, + "≢": { "codepoints": [8802], "characters": "\u2262" }, + "≭": { "codepoints": [8813], "characters": "\u226D" }, + "∦": { "codepoints": [8742], "characters": "\u2226" }, + "∉": { "codepoints": [8713], "characters": "\u2209" }, + "≠": { "codepoints": [8800], "characters": "\u2260" }, + "≂̸": { "codepoints": [8770, 824], "characters": "\u2242\u0338" }, + "∄": { "codepoints": [8708], "characters": "\u2204" }, + "≯": { "codepoints": [8815], "characters": "\u226F" }, + "≱": { "codepoints": [8817], "characters": "\u2271" }, + "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" }, + "≫̸": { "codepoints": [8811, 824], "characters": "\u226B\u0338" }, + "≹": { "codepoints": [8825], "characters": "\u2279" }, + "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" }, + "≵": { "codepoints": [8821], "characters": "\u2275" }, + "≎̸": { "codepoints": [8782, 824], "characters": "\u224E\u0338" }, + "≏̸": { "codepoints": [8783, 824], "characters": "\u224F\u0338" }, + "⋪": { "codepoints": [8938], "characters": "\u22EA" }, + "⧏̸": { "codepoints": [10703, 824], "characters": "\u29CF\u0338" }, + "⋬": { "codepoints": [8940], "characters": "\u22EC" }, + "≮": { "codepoints": [8814], "characters": "\u226E" }, + "≰": { "codepoints": [8816], "characters": "\u2270" }, + "≸": { "codepoints": [8824], "characters": "\u2278" }, + "≪̸": { "codepoints": [8810, 824], "characters": "\u226A\u0338" }, + "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" }, + "≴": { "codepoints": [8820], "characters": "\u2274" }, + "⪢̸": { "codepoints": [10914, 824], "characters": "\u2AA2\u0338" }, + "⪡̸": { "codepoints": [10913, 824], "characters": "\u2AA1\u0338" }, + "⊀": { "codepoints": [8832], "characters": "\u2280" }, + "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" }, + "⋠": { "codepoints": [8928], "characters": "\u22E0" }, + "∌": { "codepoints": [8716], "characters": "\u220C" }, + "⋫": { "codepoints": [8939], "characters": "\u22EB" }, + "⧐̸": { "codepoints": [10704, 824], "characters": "\u29D0\u0338" }, + "⋭": { "codepoints": [8941], "characters": "\u22ED" }, + "⊏̸": { "codepoints": [8847, 824], "characters": "\u228F\u0338" }, + "⋢": { "codepoints": [8930], "characters": "\u22E2" }, + "⊐̸": { "codepoints": [8848, 824], "characters": "\u2290\u0338" }, + "⋣": { "codepoints": [8931], "characters": "\u22E3" }, + "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" }, + "⊈": { "codepoints": [8840], "characters": "\u2288" }, + "⊁": { "codepoints": [8833], "characters": "\u2281" }, + "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" }, + "⋡": { "codepoints": [8929], "characters": "\u22E1" }, + "≿̸": { "codepoints": [8831, 824], "characters": "\u227F\u0338" }, + "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" }, + "⊉": { "codepoints": [8841], "characters": "\u2289" }, + "≁": { "codepoints": [8769], "characters": "\u2241" }, + "≄": { "codepoints": [8772], "characters": "\u2244" }, + "≇": { "codepoints": [8775], "characters": "\u2247" }, + "≉": { "codepoints": [8777], "characters": "\u2249" }, + "∤": { "codepoints": [8740], "characters": "\u2224" }, + "𝒩": { "codepoints": [119977], "characters": "\uD835\uDCA9" }, + "Ñ": { "codepoints": [209], "characters": "\u00D1" }, + "Ñ": { "codepoints": [209], "characters": "\u00D1" }, + "Ν": { "codepoints": [925], "characters": "\u039D" }, + "Œ": { "codepoints": [338], "characters": "\u0152" }, + "Ó": { "codepoints": [211], "characters": "\u00D3" }, + "Ó": { "codepoints": [211], "characters": "\u00D3" }, + "Ô": { "codepoints": [212], "characters": "\u00D4" }, + "Ô": { "codepoints": [212], "characters": "\u00D4" }, + "О": { "codepoints": [1054], "characters": "\u041E" }, + "Ő": { "codepoints": [336], "characters": "\u0150" }, + "𝔒": { "codepoints": [120082], "characters": "\uD835\uDD12" }, + "Ò": { "codepoints": [210], "characters": "\u00D2" }, + "Ò": { "codepoints": [210], "characters": "\u00D2" }, + "Ō": { "codepoints": [332], "characters": "\u014C" }, + "Ω": { "codepoints": [937], "characters": "\u03A9" }, + "Ο": { "codepoints": [927], "characters": "\u039F" }, + "𝕆": { "codepoints": [120134], "characters": "\uD835\uDD46" }, + "“": { "codepoints": [8220], "characters": "\u201C" }, + "‘": { "codepoints": [8216], "characters": "\u2018" }, + "⩔": { "codepoints": [10836], "characters": "\u2A54" }, + "𝒪": { "codepoints": [119978], "characters": "\uD835\uDCAA" }, + "Ø": { "codepoints": [216], "characters": "\u00D8" }, + "Ø": { "codepoints": [216], "characters": "\u00D8" }, + "Õ": { "codepoints": [213], "characters": "\u00D5" }, + "Õ": { "codepoints": [213], "characters": "\u00D5" }, + "⨷": { "codepoints": [10807], "characters": "\u2A37" }, + "Ö": { "codepoints": [214], "characters": "\u00D6" }, + "Ö": { "codepoints": [214], "characters": "\u00D6" }, + "‾": { "codepoints": [8254], "characters": "\u203E" }, + "⏞": { "codepoints": [9182], "characters": "\u23DE" }, + "⎴": { "codepoints": [9140], "characters": "\u23B4" }, + "⏜": { "codepoints": [9180], "characters": "\u23DC" }, + "∂": { "codepoints": [8706], "characters": "\u2202" }, + "П": { "codepoints": [1055], "characters": "\u041F" }, + "𝔓": { "codepoints": [120083], "characters": "\uD835\uDD13" }, + "Φ": { "codepoints": [934], "characters": "\u03A6" }, + "Π": { "codepoints": [928], "characters": "\u03A0" }, + "±": { "codepoints": [177], "characters": "\u00B1" }, + "ℌ": { "codepoints": [8460], "characters": "\u210C" }, + "ℙ": { "codepoints": [8473], "characters": "\u2119" }, + "⪻": { "codepoints": [10939], "characters": "\u2ABB" }, + "≺": { "codepoints": [8826], "characters": "\u227A" }, + "⪯": { "codepoints": [10927], "characters": "\u2AAF" }, + "≼": { "codepoints": [8828], "characters": "\u227C" }, + "≾": { "codepoints": [8830], "characters": "\u227E" }, + "″": { "codepoints": [8243], "characters": "\u2033" }, + "∏": { "codepoints": [8719], "characters": "\u220F" }, + "∷": { "codepoints": [8759], "characters": "\u2237" }, + "∝": { "codepoints": [8733], "characters": "\u221D" }, + "𝒫": { "codepoints": [119979], "characters": "\uD835\uDCAB" }, + "Ψ": { "codepoints": [936], "characters": "\u03A8" }, + """: { "codepoints": [34], "characters": "\u0022" }, + """: { "codepoints": [34], "characters": "\u0022" }, + "𝔔": { "codepoints": [120084], "characters": "\uD835\uDD14" }, + "ℚ": { "codepoints": [8474], "characters": "\u211A" }, + "𝒬": { "codepoints": [119980], "characters": "\uD835\uDCAC" }, + "⤐": { "codepoints": [10512], "characters": "\u2910" }, + "®": { "codepoints": [174], "characters": "\u00AE" }, + "®": { "codepoints": [174], "characters": "\u00AE" }, + "Ŕ": { "codepoints": [340], "characters": "\u0154" }, + "⟫": { "codepoints": [10219], "characters": "\u27EB" }, + "↠": { "codepoints": [8608], "characters": "\u21A0" }, + "⤖": { "codepoints": [10518], "characters": "\u2916" }, + "Ř": { "codepoints": [344], "characters": "\u0158" }, + "Ŗ": { "codepoints": [342], "characters": "\u0156" }, + "Р": { "codepoints": [1056], "characters": "\u0420" }, + "ℜ": { "codepoints": [8476], "characters": "\u211C" }, + "∋": { "codepoints": [8715], "characters": "\u220B" }, + "⇋": { "codepoints": [8651], "characters": "\u21CB" }, + "⥯": { "codepoints": [10607], "characters": "\u296F" }, + "ℜ": { "codepoints": [8476], "characters": "\u211C" }, + "Ρ": { "codepoints": [929], "characters": "\u03A1" }, + "⟩": { "codepoints": [10217], "characters": "\u27E9" }, + "→": { "codepoints": [8594], "characters": "\u2192" }, + "⇥": { "codepoints": [8677], "characters": "\u21E5" }, + "⇄": { "codepoints": [8644], "characters": "\u21C4" }, + "⌉": { "codepoints": [8969], "characters": "\u2309" }, + "⟧": { "codepoints": [10215], "characters": "\u27E7" }, + "⥝": { "codepoints": [10589], "characters": "\u295D" }, + "⇂": { "codepoints": [8642], "characters": "\u21C2" }, + "⥕": { "codepoints": [10581], "characters": "\u2955" }, + "⌋": { "codepoints": [8971], "characters": "\u230B" }, + "⊢": { "codepoints": [8866], "characters": "\u22A2" }, + "↦": { "codepoints": [8614], "characters": "\u21A6" }, + "⥛": { "codepoints": [10587], "characters": "\u295B" }, + "⊳": { "codepoints": [8883], "characters": "\u22B3" }, + "⧐": { "codepoints": [10704], "characters": "\u29D0" }, + "⊵": { "codepoints": [8885], "characters": "\u22B5" }, + "⥏": { "codepoints": [10575], "characters": "\u294F" }, + "⥜": { "codepoints": [10588], "characters": "\u295C" }, + "↾": { "codepoints": [8638], "characters": "\u21BE" }, + "⥔": { "codepoints": [10580], "characters": "\u2954" }, + "⇀": { "codepoints": [8640], "characters": "\u21C0" }, + "⥓": { "codepoints": [10579], "characters": "\u2953" }, + "⇒": { "codepoints": [8658], "characters": "\u21D2" }, + "ℝ": { "codepoints": [8477], "characters": "\u211D" }, + "⥰": { "codepoints": [10608], "characters": "\u2970" }, + "⇛": { "codepoints": [8667], "characters": "\u21DB" }, + "ℛ": { "codepoints": [8475], "characters": "\u211B" }, + "↱": { "codepoints": [8625], "characters": "\u21B1" }, + "⧴": { "codepoints": [10740], "characters": "\u29F4" }, + "Щ": { "codepoints": [1065], "characters": "\u0429" }, + "Ш": { "codepoints": [1064], "characters": "\u0428" }, + "Ь": { "codepoints": [1068], "characters": "\u042C" }, + "Ś": { "codepoints": [346], "characters": "\u015A" }, + "⪼": { "codepoints": [10940], "characters": "\u2ABC" }, + "Š": { "codepoints": [352], "characters": "\u0160" }, + "Ş": { "codepoints": [350], "characters": "\u015E" }, + "Ŝ": { "codepoints": [348], "characters": "\u015C" }, + "С": { "codepoints": [1057], "characters": "\u0421" }, + "𝔖": { "codepoints": [120086], "characters": "\uD835\uDD16" }, + "↓": { "codepoints": [8595], "characters": "\u2193" }, + "←": { "codepoints": [8592], "characters": "\u2190" }, + "→": { "codepoints": [8594], "characters": "\u2192" }, + "↑": { "codepoints": [8593], "characters": "\u2191" }, + "Σ": { "codepoints": [931], "characters": "\u03A3" }, + "∘": { "codepoints": [8728], "characters": "\u2218" }, + "𝕊": { "codepoints": [120138], "characters": "\uD835\uDD4A" }, + "√": { "codepoints": [8730], "characters": "\u221A" }, + "□": { "codepoints": [9633], "characters": "\u25A1" }, + "⊓": { "codepoints": [8851], "characters": "\u2293" }, + "⊏": { "codepoints": [8847], "characters": "\u228F" }, + "⊑": { "codepoints": [8849], "characters": "\u2291" }, + "⊐": { "codepoints": [8848], "characters": "\u2290" }, + "⊒": { "codepoints": [8850], "characters": "\u2292" }, + "⊔": { "codepoints": [8852], "characters": "\u2294" }, + "𝒮": { "codepoints": [119982], "characters": "\uD835\uDCAE" }, + "⋆": { "codepoints": [8902], "characters": "\u22C6" }, + "⋐": { "codepoints": [8912], "characters": "\u22D0" }, + "⋐": { "codepoints": [8912], "characters": "\u22D0" }, + "⊆": { "codepoints": [8838], "characters": "\u2286" }, + "≻": { "codepoints": [8827], "characters": "\u227B" }, + "⪰": { "codepoints": [10928], "characters": "\u2AB0" }, + "≽": { "codepoints": [8829], "characters": "\u227D" }, + "≿": { "codepoints": [8831], "characters": "\u227F" }, + "∋": { "codepoints": [8715], "characters": "\u220B" }, + "∑": { "codepoints": [8721], "characters": "\u2211" }, + "⋑": { "codepoints": [8913], "characters": "\u22D1" }, + "⊃": { "codepoints": [8835], "characters": "\u2283" }, + "⊇": { "codepoints": [8839], "characters": "\u2287" }, + "⋑": { "codepoints": [8913], "characters": "\u22D1" }, + "Þ": { "codepoints": [222], "characters": "\u00DE" }, + "Þ": { "codepoints": [222], "characters": "\u00DE" }, + "™": { "codepoints": [8482], "characters": "\u2122" }, + "Ћ": { "codepoints": [1035], "characters": "\u040B" }, + "Ц": { "codepoints": [1062], "characters": "\u0426" }, + " ": { "codepoints": [9], "characters": "\u0009" }, + "Τ": { "codepoints": [932], "characters": "\u03A4" }, + "Ť": { "codepoints": [356], "characters": "\u0164" }, + "Ţ": { "codepoints": [354], "characters": "\u0162" }, + "Т": { "codepoints": [1058], "characters": "\u0422" }, + "𝔗": { "codepoints": [120087], "characters": "\uD835\uDD17" }, + "∴": { "codepoints": [8756], "characters": "\u2234" }, + "Θ": { "codepoints": [920], "characters": "\u0398" }, + "  ": { "codepoints": [8287, 8202], "characters": "\u205F\u200A" }, + " ": { "codepoints": [8201], "characters": "\u2009" }, + "∼": { "codepoints": [8764], "characters": "\u223C" }, + "≃": { "codepoints": [8771], "characters": "\u2243" }, + "≅": { "codepoints": [8773], "characters": "\u2245" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "𝕋": { "codepoints": [120139], "characters": "\uD835\uDD4B" }, + "⃛": { "codepoints": [8411], "characters": "\u20DB" }, + "𝒯": { "codepoints": [119983], "characters": "\uD835\uDCAF" }, + "Ŧ": { "codepoints": [358], "characters": "\u0166" }, + "Ú": { "codepoints": [218], "characters": "\u00DA" }, + "Ú": { "codepoints": [218], "characters": "\u00DA" }, + "↟": { "codepoints": [8607], "characters": "\u219F" }, + "⥉": { "codepoints": [10569], "characters": "\u2949" }, + "Ў": { "codepoints": [1038], "characters": "\u040E" }, + "Ŭ": { "codepoints": [364], "characters": "\u016C" }, + "Û": { "codepoints": [219], "characters": "\u00DB" }, + "Û": { "codepoints": [219], "characters": "\u00DB" }, + "У": { "codepoints": [1059], "characters": "\u0423" }, + "Ű": { "codepoints": [368], "characters": "\u0170" }, + "𝔘": { "codepoints": [120088], "characters": "\uD835\uDD18" }, + "Ù": { "codepoints": [217], "characters": "\u00D9" }, + "Ù": { "codepoints": [217], "characters": "\u00D9" }, + "Ū": { "codepoints": [362], "characters": "\u016A" }, + "_": { "codepoints": [95], "characters": "\u005F" }, + "⏟": { "codepoints": [9183], "characters": "\u23DF" }, + "⎵": { "codepoints": [9141], "characters": "\u23B5" }, + "⏝": { "codepoints": [9181], "characters": "\u23DD" }, + "⋃": { "codepoints": [8899], "characters": "\u22C3" }, + "⊎": { "codepoints": [8846], "characters": "\u228E" }, + "Ų": { "codepoints": [370], "characters": "\u0172" }, + "𝕌": { "codepoints": [120140], "characters": "\uD835\uDD4C" }, + "↑": { "codepoints": [8593], "characters": "\u2191" }, + "⤒": { "codepoints": [10514], "characters": "\u2912" }, + "⇅": { "codepoints": [8645], "characters": "\u21C5" }, + "↕": { "codepoints": [8597], "characters": "\u2195" }, + "⥮": { "codepoints": [10606], "characters": "\u296E" }, + "⊥": { "codepoints": [8869], "characters": "\u22A5" }, + "↥": { "codepoints": [8613], "characters": "\u21A5" }, + "⇑": { "codepoints": [8657], "characters": "\u21D1" }, + "⇕": { "codepoints": [8661], "characters": "\u21D5" }, + "↖": { "codepoints": [8598], "characters": "\u2196" }, + "↗": { "codepoints": [8599], "characters": "\u2197" }, + "ϒ": { "codepoints": [978], "characters": "\u03D2" }, + "Υ": { "codepoints": [933], "characters": "\u03A5" }, + "Ů": { "codepoints": [366], "characters": "\u016E" }, + "𝒰": { "codepoints": [119984], "characters": "\uD835\uDCB0" }, + "Ũ": { "codepoints": [360], "characters": "\u0168" }, + "Ü": { "codepoints": [220], "characters": "\u00DC" }, + "Ü": { "codepoints": [220], "characters": "\u00DC" }, + "⊫": { "codepoints": [8875], "characters": "\u22AB" }, + "⫫": { "codepoints": [10987], "characters": "\u2AEB" }, + "В": { "codepoints": [1042], "characters": "\u0412" }, + "⊩": { "codepoints": [8873], "characters": "\u22A9" }, + "⫦": { "codepoints": [10982], "characters": "\u2AE6" }, + "⋁": { "codepoints": [8897], "characters": "\u22C1" }, + "‖": { "codepoints": [8214], "characters": "\u2016" }, + "‖": { "codepoints": [8214], "characters": "\u2016" }, + "∣": { "codepoints": [8739], "characters": "\u2223" }, + "|": { "codepoints": [124], "characters": "\u007C" }, + "❘": { "codepoints": [10072], "characters": "\u2758" }, + "≀": { "codepoints": [8768], "characters": "\u2240" }, + " ": { "codepoints": [8202], "characters": "\u200A" }, + "𝔙": { "codepoints": [120089], "characters": "\uD835\uDD19" }, + "𝕍": { "codepoints": [120141], "characters": "\uD835\uDD4D" }, + "𝒱": { "codepoints": [119985], "characters": "\uD835\uDCB1" }, + "⊪": { "codepoints": [8874], "characters": "\u22AA" }, + "Ŵ": { "codepoints": [372], "characters": "\u0174" }, + "⋀": { "codepoints": [8896], "characters": "\u22C0" }, + "𝔚": { "codepoints": [120090], "characters": "\uD835\uDD1A" }, + "𝕎": { "codepoints": [120142], "characters": "\uD835\uDD4E" }, + "𝒲": { "codepoints": [119986], "characters": "\uD835\uDCB2" }, + "𝔛": { "codepoints": [120091], "characters": "\uD835\uDD1B" }, + "Ξ": { "codepoints": [926], "characters": "\u039E" }, + "𝕏": { "codepoints": [120143], "characters": "\uD835\uDD4F" }, + "𝒳": { "codepoints": [119987], "characters": "\uD835\uDCB3" }, + "Я": { "codepoints": [1071], "characters": "\u042F" }, + "Ї": { "codepoints": [1031], "characters": "\u0407" }, + "Ю": { "codepoints": [1070], "characters": "\u042E" }, + "Ý": { "codepoints": [221], "characters": "\u00DD" }, + "Ý": { "codepoints": [221], "characters": "\u00DD" }, + "Ŷ": { "codepoints": [374], "characters": "\u0176" }, + "Ы": { "codepoints": [1067], "characters": "\u042B" }, + "𝔜": { "codepoints": [120092], "characters": "\uD835\uDD1C" }, + "𝕐": { "codepoints": [120144], "characters": "\uD835\uDD50" }, + "𝒴": { "codepoints": [119988], "characters": "\uD835\uDCB4" }, + "Ÿ": { "codepoints": [376], "characters": "\u0178" }, + "Ж": { "codepoints": [1046], "characters": "\u0416" }, + "Ź": { "codepoints": [377], "characters": "\u0179" }, + "Ž": { "codepoints": [381], "characters": "\u017D" }, + "З": { "codepoints": [1047], "characters": "\u0417" }, + "Ż": { "codepoints": [379], "characters": "\u017B" }, + "​": { "codepoints": [8203], "characters": "\u200B" }, + "Ζ": { "codepoints": [918], "characters": "\u0396" }, + "ℨ": { "codepoints": [8488], "characters": "\u2128" }, + "ℤ": { "codepoints": [8484], "characters": "\u2124" }, + "𝒵": { "codepoints": [119989], "characters": "\uD835\uDCB5" }, + "á": { "codepoints": [225], "characters": "\u00E1" }, + "á": { "codepoints": [225], "characters": "\u00E1" }, + "ă": { "codepoints": [259], "characters": "\u0103" }, + "∾": { "codepoints": [8766], "characters": "\u223E" }, + "∾̳": { "codepoints": [8766, 819], "characters": "\u223E\u0333" }, + "∿": { "codepoints": [8767], "characters": "\u223F" }, + "â": { "codepoints": [226], "characters": "\u00E2" }, + "â": { "codepoints": [226], "characters": "\u00E2" }, + "´": { "codepoints": [180], "characters": "\u00B4" }, + "´": { "codepoints": [180], "characters": "\u00B4" }, + "а": { "codepoints": [1072], "characters": "\u0430" }, + "æ": { "codepoints": [230], "characters": "\u00E6" }, + "æ": { "codepoints": [230], "characters": "\u00E6" }, + "⁡": { "codepoints": [8289], "characters": "\u2061" }, + "𝔞": { "codepoints": [120094], "characters": "\uD835\uDD1E" }, + "à": { "codepoints": [224], "characters": "\u00E0" }, + "à": { "codepoints": [224], "characters": "\u00E0" }, + "ℵ": { "codepoints": [8501], "characters": "\u2135" }, + "ℵ": { "codepoints": [8501], "characters": "\u2135" }, + "α": { "codepoints": [945], "characters": "\u03B1" }, + "ā": { "codepoints": [257], "characters": "\u0101" }, + "⨿": { "codepoints": [10815], "characters": "\u2A3F" }, + "&": { "codepoints": [38], "characters": "\u0026" }, + "&": { "codepoints": [38], "characters": "\u0026" }, + "∧": { "codepoints": [8743], "characters": "\u2227" }, + "⩕": { "codepoints": [10837], "characters": "\u2A55" }, + "⩜": { "codepoints": [10844], "characters": "\u2A5C" }, + "⩘": { "codepoints": [10840], "characters": "\u2A58" }, + "⩚": { "codepoints": [10842], "characters": "\u2A5A" }, + "∠": { "codepoints": [8736], "characters": "\u2220" }, + "⦤": { "codepoints": [10660], "characters": "\u29A4" }, + "∠": { "codepoints": [8736], "characters": "\u2220" }, + "∡": { "codepoints": [8737], "characters": "\u2221" }, + "⦨": { "codepoints": [10664], "characters": "\u29A8" }, + "⦩": { "codepoints": [10665], "characters": "\u29A9" }, + "⦪": { "codepoints": [10666], "characters": "\u29AA" }, + "⦫": { "codepoints": [10667], "characters": "\u29AB" }, + "⦬": { "codepoints": [10668], "characters": "\u29AC" }, + "⦭": { "codepoints": [10669], "characters": "\u29AD" }, + "⦮": { "codepoints": [10670], "characters": "\u29AE" }, + "⦯": { "codepoints": [10671], "characters": "\u29AF" }, + "∟": { "codepoints": [8735], "characters": "\u221F" }, + "⊾": { "codepoints": [8894], "characters": "\u22BE" }, + "⦝": { "codepoints": [10653], "characters": "\u299D" }, + "∢": { "codepoints": [8738], "characters": "\u2222" }, + "Å": { "codepoints": [197], "characters": "\u00C5" }, + "⍼": { "codepoints": [9084], "characters": "\u237C" }, + "ą": { "codepoints": [261], "characters": "\u0105" }, + "𝕒": { "codepoints": [120146], "characters": "\uD835\uDD52" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "⩰": { "codepoints": [10864], "characters": "\u2A70" }, + "⩯": { "codepoints": [10863], "characters": "\u2A6F" }, + "≊": { "codepoints": [8778], "characters": "\u224A" }, + "≋": { "codepoints": [8779], "characters": "\u224B" }, + "'": { "codepoints": [39], "characters": "\u0027" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "≊": { "codepoints": [8778], "characters": "\u224A" }, + "å": { "codepoints": [229], "characters": "\u00E5" }, + "å": { "codepoints": [229], "characters": "\u00E5" }, + "𝒶": { "codepoints": [119990], "characters": "\uD835\uDCB6" }, + "*": { "codepoints": [42], "characters": "\u002A" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "≍": { "codepoints": [8781], "characters": "\u224D" }, + "ã": { "codepoints": [227], "characters": "\u00E3" }, + "ã": { "codepoints": [227], "characters": "\u00E3" }, + "ä": { "codepoints": [228], "characters": "\u00E4" }, + "ä": { "codepoints": [228], "characters": "\u00E4" }, + "∳": { "codepoints": [8755], "characters": "\u2233" }, + "⨑": { "codepoints": [10769], "characters": "\u2A11" }, + "⫭": { "codepoints": [10989], "characters": "\u2AED" }, + "≌": { "codepoints": [8780], "characters": "\u224C" }, + "϶": { "codepoints": [1014], "characters": "\u03F6" }, + "‵": { "codepoints": [8245], "characters": "\u2035" }, + "∽": { "codepoints": [8765], "characters": "\u223D" }, + "⋍": { "codepoints": [8909], "characters": "\u22CD" }, + "⊽": { "codepoints": [8893], "characters": "\u22BD" }, + "⌅": { "codepoints": [8965], "characters": "\u2305" }, + "⌅": { "codepoints": [8965], "characters": "\u2305" }, + "⎵": { "codepoints": [9141], "characters": "\u23B5" }, + "⎶": { "codepoints": [9142], "characters": "\u23B6" }, + "≌": { "codepoints": [8780], "characters": "\u224C" }, + "б": { "codepoints": [1073], "characters": "\u0431" }, + "„": { "codepoints": [8222], "characters": "\u201E" }, + "∵": { "codepoints": [8757], "characters": "\u2235" }, + "∵": { "codepoints": [8757], "characters": "\u2235" }, + "⦰": { "codepoints": [10672], "characters": "\u29B0" }, + "϶": { "codepoints": [1014], "characters": "\u03F6" }, + "ℬ": { "codepoints": [8492], "characters": "\u212C" }, + "β": { "codepoints": [946], "characters": "\u03B2" }, + "ℶ": { "codepoints": [8502], "characters": "\u2136" }, + "≬": { "codepoints": [8812], "characters": "\u226C" }, + "𝔟": { "codepoints": [120095], "characters": "\uD835\uDD1F" }, + "⋂": { "codepoints": [8898], "characters": "\u22C2" }, + "◯": { "codepoints": [9711], "characters": "\u25EF" }, + "⋃": { "codepoints": [8899], "characters": "\u22C3" }, + "⨀": { "codepoints": [10752], "characters": "\u2A00" }, + "⨁": { "codepoints": [10753], "characters": "\u2A01" }, + "⨂": { "codepoints": [10754], "characters": "\u2A02" }, + "⨆": { "codepoints": [10758], "characters": "\u2A06" }, + "★": { "codepoints": [9733], "characters": "\u2605" }, + "▽": { "codepoints": [9661], "characters": "\u25BD" }, + "△": { "codepoints": [9651], "characters": "\u25B3" }, + "⨄": { "codepoints": [10756], "characters": "\u2A04" }, + "⋁": { "codepoints": [8897], "characters": "\u22C1" }, + "⋀": { "codepoints": [8896], "characters": "\u22C0" }, + "⤍": { "codepoints": [10509], "characters": "\u290D" }, + "⧫": { "codepoints": [10731], "characters": "\u29EB" }, + "▪": { "codepoints": [9642], "characters": "\u25AA" }, + "▴": { "codepoints": [9652], "characters": "\u25B4" }, + "▾": { "codepoints": [9662], "characters": "\u25BE" }, + "◂": { "codepoints": [9666], "characters": "\u25C2" }, + "▸": { "codepoints": [9656], "characters": "\u25B8" }, + "␣": { "codepoints": [9251], "characters": "\u2423" }, + "▒": { "codepoints": [9618], "characters": "\u2592" }, + "░": { "codepoints": [9617], "characters": "\u2591" }, + "▓": { "codepoints": [9619], "characters": "\u2593" }, + "█": { "codepoints": [9608], "characters": "\u2588" }, + "=⃥": { "codepoints": [61, 8421], "characters": "\u003D\u20E5" }, + "≡⃥": { "codepoints": [8801, 8421], "characters": "\u2261\u20E5" }, + "⌐": { "codepoints": [8976], "characters": "\u2310" }, + "𝕓": { "codepoints": [120147], "characters": "\uD835\uDD53" }, + "⊥": { "codepoints": [8869], "characters": "\u22A5" }, + "⊥": { "codepoints": [8869], "characters": "\u22A5" }, + "⋈": { "codepoints": [8904], "characters": "\u22C8" }, + "╗": { "codepoints": [9559], "characters": "\u2557" }, + "╔": { "codepoints": [9556], "characters": "\u2554" }, + "╖": { "codepoints": [9558], "characters": "\u2556" }, + "╓": { "codepoints": [9555], "characters": "\u2553" }, + "═": { "codepoints": [9552], "characters": "\u2550" }, + "╦": { "codepoints": [9574], "characters": "\u2566" }, + "╩": { "codepoints": [9577], "characters": "\u2569" }, + "╤": { "codepoints": [9572], "characters": "\u2564" }, + "╧": { "codepoints": [9575], "characters": "\u2567" }, + "╝": { "codepoints": [9565], "characters": "\u255D" }, + "╚": { "codepoints": [9562], "characters": "\u255A" }, + "╜": { "codepoints": [9564], "characters": "\u255C" }, + "╙": { "codepoints": [9561], "characters": "\u2559" }, + "║": { "codepoints": [9553], "characters": "\u2551" }, + "╬": { "codepoints": [9580], "characters": "\u256C" }, + "╣": { "codepoints": [9571], "characters": "\u2563" }, + "╠": { "codepoints": [9568], "characters": "\u2560" }, + "╫": { "codepoints": [9579], "characters": "\u256B" }, + "╢": { "codepoints": [9570], "characters": "\u2562" }, + "╟": { "codepoints": [9567], "characters": "\u255F" }, + "⧉": { "codepoints": [10697], "characters": "\u29C9" }, + "╕": { "codepoints": [9557], "characters": "\u2555" }, + "╒": { "codepoints": [9554], "characters": "\u2552" }, + "┐": { "codepoints": [9488], "characters": "\u2510" }, + "┌": { "codepoints": [9484], "characters": "\u250C" }, + "─": { "codepoints": [9472], "characters": "\u2500" }, + "╥": { "codepoints": [9573], "characters": "\u2565" }, + "╨": { "codepoints": [9576], "characters": "\u2568" }, + "┬": { "codepoints": [9516], "characters": "\u252C" }, + "┴": { "codepoints": [9524], "characters": "\u2534" }, + "⊟": { "codepoints": [8863], "characters": "\u229F" }, + "⊞": { "codepoints": [8862], "characters": "\u229E" }, + "⊠": { "codepoints": [8864], "characters": "\u22A0" }, + "╛": { "codepoints": [9563], "characters": "\u255B" }, + "╘": { "codepoints": [9560], "characters": "\u2558" }, + "┘": { "codepoints": [9496], "characters": "\u2518" }, + "└": { "codepoints": [9492], "characters": "\u2514" }, + "│": { "codepoints": [9474], "characters": "\u2502" }, + "╪": { "codepoints": [9578], "characters": "\u256A" }, + "╡": { "codepoints": [9569], "characters": "\u2561" }, + "╞": { "codepoints": [9566], "characters": "\u255E" }, + "┼": { "codepoints": [9532], "characters": "\u253C" }, + "┤": { "codepoints": [9508], "characters": "\u2524" }, + "├": { "codepoints": [9500], "characters": "\u251C" }, + "‵": { "codepoints": [8245], "characters": "\u2035" }, + "˘": { "codepoints": [728], "characters": "\u02D8" }, + "¦": { "codepoints": [166], "characters": "\u00A6" }, + "¦": { "codepoints": [166], "characters": "\u00A6" }, + "𝒷": { "codepoints": [119991], "characters": "\uD835\uDCB7" }, + "⁏": { "codepoints": [8271], "characters": "\u204F" }, + "∽": { "codepoints": [8765], "characters": "\u223D" }, + "⋍": { "codepoints": [8909], "characters": "\u22CD" }, + "\": { "codepoints": [92], "characters": "\u005C" }, + "⧅": { "codepoints": [10693], "characters": "\u29C5" }, + "⟈": { "codepoints": [10184], "characters": "\u27C8" }, + "•": { "codepoints": [8226], "characters": "\u2022" }, + "•": { "codepoints": [8226], "characters": "\u2022" }, + "≎": { "codepoints": [8782], "characters": "\u224E" }, + "⪮": { "codepoints": [10926], "characters": "\u2AAE" }, + "≏": { "codepoints": [8783], "characters": "\u224F" }, + "≏": { "codepoints": [8783], "characters": "\u224F" }, + "ć": { "codepoints": [263], "characters": "\u0107" }, + "∩": { "codepoints": [8745], "characters": "\u2229" }, + "⩄": { "codepoints": [10820], "characters": "\u2A44" }, + "⩉": { "codepoints": [10825], "characters": "\u2A49" }, + "⩋": { "codepoints": [10827], "characters": "\u2A4B" }, + "⩇": { "codepoints": [10823], "characters": "\u2A47" }, + "⩀": { "codepoints": [10816], "characters": "\u2A40" }, + "∩︀": { "codepoints": [8745, 65024], "characters": "\u2229\uFE00" }, + "⁁": { "codepoints": [8257], "characters": "\u2041" }, + "ˇ": { "codepoints": [711], "characters": "\u02C7" }, + "⩍": { "codepoints": [10829], "characters": "\u2A4D" }, + "č": { "codepoints": [269], "characters": "\u010D" }, + "ç": { "codepoints": [231], "characters": "\u00E7" }, + "ç": { "codepoints": [231], "characters": "\u00E7" }, + "ĉ": { "codepoints": [265], "characters": "\u0109" }, + "⩌": { "codepoints": [10828], "characters": "\u2A4C" }, + "⩐": { "codepoints": [10832], "characters": "\u2A50" }, + "ċ": { "codepoints": [267], "characters": "\u010B" }, + "¸": { "codepoints": [184], "characters": "\u00B8" }, + "¸": { "codepoints": [184], "characters": "\u00B8" }, + "⦲": { "codepoints": [10674], "characters": "\u29B2" }, + "¢": { "codepoints": [162], "characters": "\u00A2" }, + "¢": { "codepoints": [162], "characters": "\u00A2" }, + "·": { "codepoints": [183], "characters": "\u00B7" }, + "𝔠": { "codepoints": [120096], "characters": "\uD835\uDD20" }, + "ч": { "codepoints": [1095], "characters": "\u0447" }, + "✓": { "codepoints": [10003], "characters": "\u2713" }, + "✓": { "codepoints": [10003], "characters": "\u2713" }, + "χ": { "codepoints": [967], "characters": "\u03C7" }, + "○": { "codepoints": [9675], "characters": "\u25CB" }, + "⧃": { "codepoints": [10691], "characters": "\u29C3" }, + "ˆ": { "codepoints": [710], "characters": "\u02C6" }, + "≗": { "codepoints": [8791], "characters": "\u2257" }, + "↺": { "codepoints": [8634], "characters": "\u21BA" }, + "↻": { "codepoints": [8635], "characters": "\u21BB" }, + "®": { "codepoints": [174], "characters": "\u00AE" }, + "Ⓢ": { "codepoints": [9416], "characters": "\u24C8" }, + "⊛": { "codepoints": [8859], "characters": "\u229B" }, + "⊚": { "codepoints": [8858], "characters": "\u229A" }, + "⊝": { "codepoints": [8861], "characters": "\u229D" }, + "≗": { "codepoints": [8791], "characters": "\u2257" }, + "⨐": { "codepoints": [10768], "characters": "\u2A10" }, + "⫯": { "codepoints": [10991], "characters": "\u2AEF" }, + "⧂": { "codepoints": [10690], "characters": "\u29C2" }, + "♣": { "codepoints": [9827], "characters": "\u2663" }, + "♣": { "codepoints": [9827], "characters": "\u2663" }, + ":": { "codepoints": [58], "characters": "\u003A" }, + "≔": { "codepoints": [8788], "characters": "\u2254" }, + "≔": { "codepoints": [8788], "characters": "\u2254" }, + ",": { "codepoints": [44], "characters": "\u002C" }, + "@": { "codepoints": [64], "characters": "\u0040" }, + "∁": { "codepoints": [8705], "characters": "\u2201" }, + "∘": { "codepoints": [8728], "characters": "\u2218" }, + "∁": { "codepoints": [8705], "characters": "\u2201" }, + "ℂ": { "codepoints": [8450], "characters": "\u2102" }, + "≅": { "codepoints": [8773], "characters": "\u2245" }, + "⩭": { "codepoints": [10861], "characters": "\u2A6D" }, + "∮": { "codepoints": [8750], "characters": "\u222E" }, + "𝕔": { "codepoints": [120148], "characters": "\uD835\uDD54" }, + "∐": { "codepoints": [8720], "characters": "\u2210" }, + "©": { "codepoints": [169], "characters": "\u00A9" }, + "©": { "codepoints": [169], "characters": "\u00A9" }, + "℗": { "codepoints": [8471], "characters": "\u2117" }, + "↵": { "codepoints": [8629], "characters": "\u21B5" }, + "✗": { "codepoints": [10007], "characters": "\u2717" }, + "𝒸": { "codepoints": [119992], "characters": "\uD835\uDCB8" }, + "⫏": { "codepoints": [10959], "characters": "\u2ACF" }, + "⫑": { "codepoints": [10961], "characters": "\u2AD1" }, + "⫐": { "codepoints": [10960], "characters": "\u2AD0" }, + "⫒": { "codepoints": [10962], "characters": "\u2AD2" }, + "⋯": { "codepoints": [8943], "characters": "\u22EF" }, + "⤸": { "codepoints": [10552], "characters": "\u2938" }, + "⤵": { "codepoints": [10549], "characters": "\u2935" }, + "⋞": { "codepoints": [8926], "characters": "\u22DE" }, + "⋟": { "codepoints": [8927], "characters": "\u22DF" }, + "↶": { "codepoints": [8630], "characters": "\u21B6" }, + "⤽": { "codepoints": [10557], "characters": "\u293D" }, + "∪": { "codepoints": [8746], "characters": "\u222A" }, + "⩈": { "codepoints": [10824], "characters": "\u2A48" }, + "⩆": { "codepoints": [10822], "characters": "\u2A46" }, + "⩊": { "codepoints": [10826], "characters": "\u2A4A" }, + "⊍": { "codepoints": [8845], "characters": "\u228D" }, + "⩅": { "codepoints": [10821], "characters": "\u2A45" }, + "∪︀": { "codepoints": [8746, 65024], "characters": "\u222A\uFE00" }, + "↷": { "codepoints": [8631], "characters": "\u21B7" }, + "⤼": { "codepoints": [10556], "characters": "\u293C" }, + "⋞": { "codepoints": [8926], "characters": "\u22DE" }, + "⋟": { "codepoints": [8927], "characters": "\u22DF" }, + "⋎": { "codepoints": [8910], "characters": "\u22CE" }, + "⋏": { "codepoints": [8911], "characters": "\u22CF" }, + "¤": { "codepoints": [164], "characters": "\u00A4" }, + "¤": { "codepoints": [164], "characters": "\u00A4" }, + "↶": { "codepoints": [8630], "characters": "\u21B6" }, + "↷": { "codepoints": [8631], "characters": "\u21B7" }, + "⋎": { "codepoints": [8910], "characters": "\u22CE" }, + "⋏": { "codepoints": [8911], "characters": "\u22CF" }, + "∲": { "codepoints": [8754], "characters": "\u2232" }, + "∱": { "codepoints": [8753], "characters": "\u2231" }, + "⌭": { "codepoints": [9005], "characters": "\u232D" }, + "⇓": { "codepoints": [8659], "characters": "\u21D3" }, + "⥥": { "codepoints": [10597], "characters": "\u2965" }, + "†": { "codepoints": [8224], "characters": "\u2020" }, + "ℸ": { "codepoints": [8504], "characters": "\u2138" }, + "↓": { "codepoints": [8595], "characters": "\u2193" }, + "‐": { "codepoints": [8208], "characters": "\u2010" }, + "⊣": { "codepoints": [8867], "characters": "\u22A3" }, + "⤏": { "codepoints": [10511], "characters": "\u290F" }, + "˝": { "codepoints": [733], "characters": "\u02DD" }, + "ď": { "codepoints": [271], "characters": "\u010F" }, + "д": { "codepoints": [1076], "characters": "\u0434" }, + "ⅆ": { "codepoints": [8518], "characters": "\u2146" }, + "‡": { "codepoints": [8225], "characters": "\u2021" }, + "⇊": { "codepoints": [8650], "characters": "\u21CA" }, + "⩷": { "codepoints": [10871], "characters": "\u2A77" }, + "°": { "codepoints": [176], "characters": "\u00B0" }, + "°": { "codepoints": [176], "characters": "\u00B0" }, + "δ": { "codepoints": [948], "characters": "\u03B4" }, + "⦱": { "codepoints": [10673], "characters": "\u29B1" }, + "⥿": { "codepoints": [10623], "characters": "\u297F" }, + "𝔡": { "codepoints": [120097], "characters": "\uD835\uDD21" }, + "⇃": { "codepoints": [8643], "characters": "\u21C3" }, + "⇂": { "codepoints": [8642], "characters": "\u21C2" }, + "⋄": { "codepoints": [8900], "characters": "\u22C4" }, + "⋄": { "codepoints": [8900], "characters": "\u22C4" }, + "♦": { "codepoints": [9830], "characters": "\u2666" }, + "♦": { "codepoints": [9830], "characters": "\u2666" }, + "¨": { "codepoints": [168], "characters": "\u00A8" }, + "ϝ": { "codepoints": [989], "characters": "\u03DD" }, + "⋲": { "codepoints": [8946], "characters": "\u22F2" }, + "÷": { "codepoints": [247], "characters": "\u00F7" }, + "÷": { "codepoints": [247], "characters": "\u00F7" }, + "÷": { "codepoints": [247], "characters": "\u00F7" }, + "⋇": { "codepoints": [8903], "characters": "\u22C7" }, + "⋇": { "codepoints": [8903], "characters": "\u22C7" }, + "ђ": { "codepoints": [1106], "characters": "\u0452" }, + "⌞": { "codepoints": [8990], "characters": "\u231E" }, + "⌍": { "codepoints": [8973], "characters": "\u230D" }, + "$": { "codepoints": [36], "characters": "\u0024" }, + "𝕕": { "codepoints": [120149], "characters": "\uD835\uDD55" }, + "˙": { "codepoints": [729], "characters": "\u02D9" }, + "≐": { "codepoints": [8784], "characters": "\u2250" }, + "≑": { "codepoints": [8785], "characters": "\u2251" }, + "∸": { "codepoints": [8760], "characters": "\u2238" }, + "∔": { "codepoints": [8724], "characters": "\u2214" }, + "⊡": { "codepoints": [8865], "characters": "\u22A1" }, + "⌆": { "codepoints": [8966], "characters": "\u2306" }, + "↓": { "codepoints": [8595], "characters": "\u2193" }, + "⇊": { "codepoints": [8650], "characters": "\u21CA" }, + "⇃": { "codepoints": [8643], "characters": "\u21C3" }, + "⇂": { "codepoints": [8642], "characters": "\u21C2" }, + "⤐": { "codepoints": [10512], "characters": "\u2910" }, + "⌟": { "codepoints": [8991], "characters": "\u231F" }, + "⌌": { "codepoints": [8972], "characters": "\u230C" }, + "𝒹": { "codepoints": [119993], "characters": "\uD835\uDCB9" }, + "ѕ": { "codepoints": [1109], "characters": "\u0455" }, + "⧶": { "codepoints": [10742], "characters": "\u29F6" }, + "đ": { "codepoints": [273], "characters": "\u0111" }, + "⋱": { "codepoints": [8945], "characters": "\u22F1" }, + "▿": { "codepoints": [9663], "characters": "\u25BF" }, + "▾": { "codepoints": [9662], "characters": "\u25BE" }, + "⇵": { "codepoints": [8693], "characters": "\u21F5" }, + "⥯": { "codepoints": [10607], "characters": "\u296F" }, + "⦦": { "codepoints": [10662], "characters": "\u29A6" }, + "џ": { "codepoints": [1119], "characters": "\u045F" }, + "⟿": { "codepoints": [10239], "characters": "\u27FF" }, + "⩷": { "codepoints": [10871], "characters": "\u2A77" }, + "≑": { "codepoints": [8785], "characters": "\u2251" }, + "é": { "codepoints": [233], "characters": "\u00E9" }, + "é": { "codepoints": [233], "characters": "\u00E9" }, + "⩮": { "codepoints": [10862], "characters": "\u2A6E" }, + "ě": { "codepoints": [283], "characters": "\u011B" }, + "≖": { "codepoints": [8790], "characters": "\u2256" }, + "ê": { "codepoints": [234], "characters": "\u00EA" }, + "ê": { "codepoints": [234], "characters": "\u00EA" }, + "≕": { "codepoints": [8789], "characters": "\u2255" }, + "э": { "codepoints": [1101], "characters": "\u044D" }, + "ė": { "codepoints": [279], "characters": "\u0117" }, + "ⅇ": { "codepoints": [8519], "characters": "\u2147" }, + "≒": { "codepoints": [8786], "characters": "\u2252" }, + "𝔢": { "codepoints": [120098], "characters": "\uD835\uDD22" }, + "⪚": { "codepoints": [10906], "characters": "\u2A9A" }, + "è": { "codepoints": [232], "characters": "\u00E8" }, + "è": { "codepoints": [232], "characters": "\u00E8" }, + "⪖": { "codepoints": [10902], "characters": "\u2A96" }, + "⪘": { "codepoints": [10904], "characters": "\u2A98" }, + "⪙": { "codepoints": [10905], "characters": "\u2A99" }, + "⏧": { "codepoints": [9191], "characters": "\u23E7" }, + "ℓ": { "codepoints": [8467], "characters": "\u2113" }, + "⪕": { "codepoints": [10901], "characters": "\u2A95" }, + "⪗": { "codepoints": [10903], "characters": "\u2A97" }, + "ē": { "codepoints": [275], "characters": "\u0113" }, + "∅": { "codepoints": [8709], "characters": "\u2205" }, + "∅": { "codepoints": [8709], "characters": "\u2205" }, + "∅": { "codepoints": [8709], "characters": "\u2205" }, + " ": { "codepoints": [8196], "characters": "\u2004" }, + " ": { "codepoints": [8197], "characters": "\u2005" }, + " ": { "codepoints": [8195], "characters": "\u2003" }, + "ŋ": { "codepoints": [331], "characters": "\u014B" }, + " ": { "codepoints": [8194], "characters": "\u2002" }, + "ę": { "codepoints": [281], "characters": "\u0119" }, + "𝕖": { "codepoints": [120150], "characters": "\uD835\uDD56" }, + "⋕": { "codepoints": [8917], "characters": "\u22D5" }, + "⧣": { "codepoints": [10723], "characters": "\u29E3" }, + "⩱": { "codepoints": [10865], "characters": "\u2A71" }, + "ε": { "codepoints": [949], "characters": "\u03B5" }, + "ε": { "codepoints": [949], "characters": "\u03B5" }, + "ϵ": { "codepoints": [1013], "characters": "\u03F5" }, + "≖": { "codepoints": [8790], "characters": "\u2256" }, + "≕": { "codepoints": [8789], "characters": "\u2255" }, + "≂": { "codepoints": [8770], "characters": "\u2242" }, + "⪖": { "codepoints": [10902], "characters": "\u2A96" }, + "⪕": { "codepoints": [10901], "characters": "\u2A95" }, + "=": { "codepoints": [61], "characters": "\u003D" }, + "≟": { "codepoints": [8799], "characters": "\u225F" }, + "≡": { "codepoints": [8801], "characters": "\u2261" }, + "⩸": { "codepoints": [10872], "characters": "\u2A78" }, + "⧥": { "codepoints": [10725], "characters": "\u29E5" }, + "≓": { "codepoints": [8787], "characters": "\u2253" }, + "⥱": { "codepoints": [10609], "characters": "\u2971" }, + "ℯ": { "codepoints": [8495], "characters": "\u212F" }, + "≐": { "codepoints": [8784], "characters": "\u2250" }, + "≂": { "codepoints": [8770], "characters": "\u2242" }, + "η": { "codepoints": [951], "characters": "\u03B7" }, + "ð": { "codepoints": [240], "characters": "\u00F0" }, + "ð": { "codepoints": [240], "characters": "\u00F0" }, + "ë": { "codepoints": [235], "characters": "\u00EB" }, + "ë": { "codepoints": [235], "characters": "\u00EB" }, + "€": { "codepoints": [8364], "characters": "\u20AC" }, + "!": { "codepoints": [33], "characters": "\u0021" }, + "∃": { "codepoints": [8707], "characters": "\u2203" }, + "ℰ": { "codepoints": [8496], "characters": "\u2130" }, + "ⅇ": { "codepoints": [8519], "characters": "\u2147" }, + "≒": { "codepoints": [8786], "characters": "\u2252" }, + "ф": { "codepoints": [1092], "characters": "\u0444" }, + "♀": { "codepoints": [9792], "characters": "\u2640" }, + "ffi": { "codepoints": [64259], "characters": "\uFB03" }, + "ff": { "codepoints": [64256], "characters": "\uFB00" }, + "ffl": { "codepoints": [64260], "characters": "\uFB04" }, + "𝔣": { "codepoints": [120099], "characters": "\uD835\uDD23" }, + "fi": { "codepoints": [64257], "characters": "\uFB01" }, + "fj": { "codepoints": [102, 106], "characters": "\u0066\u006A" }, + "♭": { "codepoints": [9837], "characters": "\u266D" }, + "fl": { "codepoints": [64258], "characters": "\uFB02" }, + "▱": { "codepoints": [9649], "characters": "\u25B1" }, + "ƒ": { "codepoints": [402], "characters": "\u0192" }, + "𝕗": { "codepoints": [120151], "characters": "\uD835\uDD57" }, + "∀": { "codepoints": [8704], "characters": "\u2200" }, + "⋔": { "codepoints": [8916], "characters": "\u22D4" }, + "⫙": { "codepoints": [10969], "characters": "\u2AD9" }, + "⨍": { "codepoints": [10765], "characters": "\u2A0D" }, + "½": { "codepoints": [189], "characters": "\u00BD" }, + "½": { "codepoints": [189], "characters": "\u00BD" }, + "⅓": { "codepoints": [8531], "characters": "\u2153" }, + "¼": { "codepoints": [188], "characters": "\u00BC" }, + "¼": { "codepoints": [188], "characters": "\u00BC" }, + "⅕": { "codepoints": [8533], "characters": "\u2155" }, + "⅙": { "codepoints": [8537], "characters": "\u2159" }, + "⅛": { "codepoints": [8539], "characters": "\u215B" }, + "⅔": { "codepoints": [8532], "characters": "\u2154" }, + "⅖": { "codepoints": [8534], "characters": "\u2156" }, + "¾": { "codepoints": [190], "characters": "\u00BE" }, + "¾": { "codepoints": [190], "characters": "\u00BE" }, + "⅗": { "codepoints": [8535], "characters": "\u2157" }, + "⅜": { "codepoints": [8540], "characters": "\u215C" }, + "⅘": { "codepoints": [8536], "characters": "\u2158" }, + "⅚": { "codepoints": [8538], "characters": "\u215A" }, + "⅝": { "codepoints": [8541], "characters": "\u215D" }, + "⅞": { "codepoints": [8542], "characters": "\u215E" }, + "⁄": { "codepoints": [8260], "characters": "\u2044" }, + "⌢": { "codepoints": [8994], "characters": "\u2322" }, + "𝒻": { "codepoints": [119995], "characters": "\uD835\uDCBB" }, + "≧": { "codepoints": [8807], "characters": "\u2267" }, + "⪌": { "codepoints": [10892], "characters": "\u2A8C" }, + "ǵ": { "codepoints": [501], "characters": "\u01F5" }, + "γ": { "codepoints": [947], "characters": "\u03B3" }, + "ϝ": { "codepoints": [989], "characters": "\u03DD" }, + "⪆": { "codepoints": [10886], "characters": "\u2A86" }, + "ğ": { "codepoints": [287], "characters": "\u011F" }, + "ĝ": { "codepoints": [285], "characters": "\u011D" }, + "г": { "codepoints": [1075], "characters": "\u0433" }, + "ġ": { "codepoints": [289], "characters": "\u0121" }, + "≥": { "codepoints": [8805], "characters": "\u2265" }, + "⋛": { "codepoints": [8923], "characters": "\u22DB" }, + "≥": { "codepoints": [8805], "characters": "\u2265" }, + "≧": { "codepoints": [8807], "characters": "\u2267" }, + "⩾": { "codepoints": [10878], "characters": "\u2A7E" }, + "⩾": { "codepoints": [10878], "characters": "\u2A7E" }, + "⪩": { "codepoints": [10921], "characters": "\u2AA9" }, + "⪀": { "codepoints": [10880], "characters": "\u2A80" }, + "⪂": { "codepoints": [10882], "characters": "\u2A82" }, + "⪄": { "codepoints": [10884], "characters": "\u2A84" }, + "⋛︀": { "codepoints": [8923, 65024], "characters": "\u22DB\uFE00" }, + "⪔": { "codepoints": [10900], "characters": "\u2A94" }, + "𝔤": { "codepoints": [120100], "characters": "\uD835\uDD24" }, + "≫": { "codepoints": [8811], "characters": "\u226B" }, + "⋙": { "codepoints": [8921], "characters": "\u22D9" }, + "ℷ": { "codepoints": [8503], "characters": "\u2137" }, + "ѓ": { "codepoints": [1107], "characters": "\u0453" }, + "≷": { "codepoints": [8823], "characters": "\u2277" }, + "⪒": { "codepoints": [10898], "characters": "\u2A92" }, + "⪥": { "codepoints": [10917], "characters": "\u2AA5" }, + "⪤": { "codepoints": [10916], "characters": "\u2AA4" }, + "≩": { "codepoints": [8809], "characters": "\u2269" }, + "⪊": { "codepoints": [10890], "characters": "\u2A8A" }, + "⪊": { "codepoints": [10890], "characters": "\u2A8A" }, + "⪈": { "codepoints": [10888], "characters": "\u2A88" }, + "⪈": { "codepoints": [10888], "characters": "\u2A88" }, + "≩": { "codepoints": [8809], "characters": "\u2269" }, + "⋧": { "codepoints": [8935], "characters": "\u22E7" }, + "𝕘": { "codepoints": [120152], "characters": "\uD835\uDD58" }, + "`": { "codepoints": [96], "characters": "\u0060" }, + "ℊ": { "codepoints": [8458], "characters": "\u210A" }, + "≳": { "codepoints": [8819], "characters": "\u2273" }, + "⪎": { "codepoints": [10894], "characters": "\u2A8E" }, + "⪐": { "codepoints": [10896], "characters": "\u2A90" }, + ">": { "codepoints": [62], "characters": "\u003E" }, + ">": { "codepoints": [62], "characters": "\u003E" }, + "⪧": { "codepoints": [10919], "characters": "\u2AA7" }, + "⩺": { "codepoints": [10874], "characters": "\u2A7A" }, + "⋗": { "codepoints": [8919], "characters": "\u22D7" }, + "⦕": { "codepoints": [10645], "characters": "\u2995" }, + "⩼": { "codepoints": [10876], "characters": "\u2A7C" }, + "⪆": { "codepoints": [10886], "characters": "\u2A86" }, + "⥸": { "codepoints": [10616], "characters": "\u2978" }, + "⋗": { "codepoints": [8919], "characters": "\u22D7" }, + "⋛": { "codepoints": [8923], "characters": "\u22DB" }, + "⪌": { "codepoints": [10892], "characters": "\u2A8C" }, + "≷": { "codepoints": [8823], "characters": "\u2277" }, + "≳": { "codepoints": [8819], "characters": "\u2273" }, + "≩︀": { "codepoints": [8809, 65024], "characters": "\u2269\uFE00" }, + "≩︀": { "codepoints": [8809, 65024], "characters": "\u2269\uFE00" }, + "⇔": { "codepoints": [8660], "characters": "\u21D4" }, + " ": { "codepoints": [8202], "characters": "\u200A" }, + "½": { "codepoints": [189], "characters": "\u00BD" }, + "ℋ": { "codepoints": [8459], "characters": "\u210B" }, + "ъ": { "codepoints": [1098], "characters": "\u044A" }, + "↔": { "codepoints": [8596], "characters": "\u2194" }, + "⥈": { "codepoints": [10568], "characters": "\u2948" }, + "↭": { "codepoints": [8621], "characters": "\u21AD" }, + "ℏ": { "codepoints": [8463], "characters": "\u210F" }, + "ĥ": { "codepoints": [293], "characters": "\u0125" }, + "♥": { "codepoints": [9829], "characters": "\u2665" }, + "♥": { "codepoints": [9829], "characters": "\u2665" }, + "…": { "codepoints": [8230], "characters": "\u2026" }, + "⊹": { "codepoints": [8889], "characters": "\u22B9" }, + "𝔥": { "codepoints": [120101], "characters": "\uD835\uDD25" }, + "⤥": { "codepoints": [10533], "characters": "\u2925" }, + "⤦": { "codepoints": [10534], "characters": "\u2926" }, + "⇿": { "codepoints": [8703], "characters": "\u21FF" }, + "∻": { "codepoints": [8763], "characters": "\u223B" }, + "↩": { "codepoints": [8617], "characters": "\u21A9" }, + "↪": { "codepoints": [8618], "characters": "\u21AA" }, + "𝕙": { "codepoints": [120153], "characters": "\uD835\uDD59" }, + "―": { "codepoints": [8213], "characters": "\u2015" }, + "𝒽": { "codepoints": [119997], "characters": "\uD835\uDCBD" }, + "ℏ": { "codepoints": [8463], "characters": "\u210F" }, + "ħ": { "codepoints": [295], "characters": "\u0127" }, + "⁃": { "codepoints": [8259], "characters": "\u2043" }, + "‐": { "codepoints": [8208], "characters": "\u2010" }, + "í": { "codepoints": [237], "characters": "\u00ED" }, + "í": { "codepoints": [237], "characters": "\u00ED" }, + "⁣": { "codepoints": [8291], "characters": "\u2063" }, + "î": { "codepoints": [238], "characters": "\u00EE" }, + "î": { "codepoints": [238], "characters": "\u00EE" }, + "и": { "codepoints": [1080], "characters": "\u0438" }, + "е": { "codepoints": [1077], "characters": "\u0435" }, + "¡": { "codepoints": [161], "characters": "\u00A1" }, + "¡": { "codepoints": [161], "characters": "\u00A1" }, + "⇔": { "codepoints": [8660], "characters": "\u21D4" }, + "𝔦": { "codepoints": [120102], "characters": "\uD835\uDD26" }, + "ì": { "codepoints": [236], "characters": "\u00EC" }, + "ì": { "codepoints": [236], "characters": "\u00EC" }, + "ⅈ": { "codepoints": [8520], "characters": "\u2148" }, + "⨌": { "codepoints": [10764], "characters": "\u2A0C" }, + "∭": { "codepoints": [8749], "characters": "\u222D" }, + "⧜": { "codepoints": [10716], "characters": "\u29DC" }, + "℩": { "codepoints": [8489], "characters": "\u2129" }, + "ij": { "codepoints": [307], "characters": "\u0133" }, + "ī": { "codepoints": [299], "characters": "\u012B" }, + "ℑ": { "codepoints": [8465], "characters": "\u2111" }, + "ℐ": { "codepoints": [8464], "characters": "\u2110" }, + "ℑ": { "codepoints": [8465], "characters": "\u2111" }, + "ı": { "codepoints": [305], "characters": "\u0131" }, + "⊷": { "codepoints": [8887], "characters": "\u22B7" }, + "Ƶ": { "codepoints": [437], "characters": "\u01B5" }, + "∈": { "codepoints": [8712], "characters": "\u2208" }, + "℅": { "codepoints": [8453], "characters": "\u2105" }, + "∞": { "codepoints": [8734], "characters": "\u221E" }, + "⧝": { "codepoints": [10717], "characters": "\u29DD" }, + "ı": { "codepoints": [305], "characters": "\u0131" }, + "∫": { "codepoints": [8747], "characters": "\u222B" }, + "⊺": { "codepoints": [8890], "characters": "\u22BA" }, + "ℤ": { "codepoints": [8484], "characters": "\u2124" }, + "⊺": { "codepoints": [8890], "characters": "\u22BA" }, + "⨗": { "codepoints": [10775], "characters": "\u2A17" }, + "⨼": { "codepoints": [10812], "characters": "\u2A3C" }, + "ё": { "codepoints": [1105], "characters": "\u0451" }, + "į": { "codepoints": [303], "characters": "\u012F" }, + "𝕚": { "codepoints": [120154], "characters": "\uD835\uDD5A" }, + "ι": { "codepoints": [953], "characters": "\u03B9" }, + "⨼": { "codepoints": [10812], "characters": "\u2A3C" }, + "¿": { "codepoints": [191], "characters": "\u00BF" }, + "¿": { "codepoints": [191], "characters": "\u00BF" }, + "𝒾": { "codepoints": [119998], "characters": "\uD835\uDCBE" }, + "∈": { "codepoints": [8712], "characters": "\u2208" }, + "⋹": { "codepoints": [8953], "characters": "\u22F9" }, + "⋵": { "codepoints": [8949], "characters": "\u22F5" }, + "⋴": { "codepoints": [8948], "characters": "\u22F4" }, + "⋳": { "codepoints": [8947], "characters": "\u22F3" }, + "∈": { "codepoints": [8712], "characters": "\u2208" }, + "⁢": { "codepoints": [8290], "characters": "\u2062" }, + "ĩ": { "codepoints": [297], "characters": "\u0129" }, + "і": { "codepoints": [1110], "characters": "\u0456" }, + "ï": { "codepoints": [239], "characters": "\u00EF" }, + "ï": { "codepoints": [239], "characters": "\u00EF" }, + "ĵ": { "codepoints": [309], "characters": "\u0135" }, + "й": { "codepoints": [1081], "characters": "\u0439" }, + "𝔧": { "codepoints": [120103], "characters": "\uD835\uDD27" }, + "ȷ": { "codepoints": [567], "characters": "\u0237" }, + "𝕛": { "codepoints": [120155], "characters": "\uD835\uDD5B" }, + "𝒿": { "codepoints": [119999], "characters": "\uD835\uDCBF" }, + "ј": { "codepoints": [1112], "characters": "\u0458" }, + "є": { "codepoints": [1108], "characters": "\u0454" }, + "κ": { "codepoints": [954], "characters": "\u03BA" }, + "ϰ": { "codepoints": [1008], "characters": "\u03F0" }, + "ķ": { "codepoints": [311], "characters": "\u0137" }, + "к": { "codepoints": [1082], "characters": "\u043A" }, + "𝔨": { "codepoints": [120104], "characters": "\uD835\uDD28" }, + "ĸ": { "codepoints": [312], "characters": "\u0138" }, + "х": { "codepoints": [1093], "characters": "\u0445" }, + "ќ": { "codepoints": [1116], "characters": "\u045C" }, + "𝕜": { "codepoints": [120156], "characters": "\uD835\uDD5C" }, + "𝓀": { "codepoints": [120000], "characters": "\uD835\uDCC0" }, + "⇚": { "codepoints": [8666], "characters": "\u21DA" }, + "⇐": { "codepoints": [8656], "characters": "\u21D0" }, + "⤛": { "codepoints": [10523], "characters": "\u291B" }, + "⤎": { "codepoints": [10510], "characters": "\u290E" }, + "≦": { "codepoints": [8806], "characters": "\u2266" }, + "⪋": { "codepoints": [10891], "characters": "\u2A8B" }, + "⥢": { "codepoints": [10594], "characters": "\u2962" }, + "ĺ": { "codepoints": [314], "characters": "\u013A" }, + "⦴": { "codepoints": [10676], "characters": "\u29B4" }, + "ℒ": { "codepoints": [8466], "characters": "\u2112" }, + "λ": { "codepoints": [955], "characters": "\u03BB" }, + "⟨": { "codepoints": [10216], "characters": "\u27E8" }, + "⦑": { "codepoints": [10641], "characters": "\u2991" }, + "⟨": { "codepoints": [10216], "characters": "\u27E8" }, + "⪅": { "codepoints": [10885], "characters": "\u2A85" }, + "«": { "codepoints": [171], "characters": "\u00AB" }, + "«": { "codepoints": [171], "characters": "\u00AB" }, + "←": { "codepoints": [8592], "characters": "\u2190" }, + "⇤": { "codepoints": [8676], "characters": "\u21E4" }, + "⤟": { "codepoints": [10527], "characters": "\u291F" }, + "⤝": { "codepoints": [10525], "characters": "\u291D" }, + "↩": { "codepoints": [8617], "characters": "\u21A9" }, + "↫": { "codepoints": [8619], "characters": "\u21AB" }, + "⤹": { "codepoints": [10553], "characters": "\u2939" }, + "⥳": { "codepoints": [10611], "characters": "\u2973" }, + "↢": { "codepoints": [8610], "characters": "\u21A2" }, + "⪫": { "codepoints": [10923], "characters": "\u2AAB" }, + "⤙": { "codepoints": [10521], "characters": "\u2919" }, + "⪭": { "codepoints": [10925], "characters": "\u2AAD" }, + "⪭︀": { "codepoints": [10925, 65024], "characters": "\u2AAD\uFE00" }, + "⤌": { "codepoints": [10508], "characters": "\u290C" }, + "❲": { "codepoints": [10098], "characters": "\u2772" }, + "{": { "codepoints": [123], "characters": "\u007B" }, + "[": { "codepoints": [91], "characters": "\u005B" }, + "⦋": { "codepoints": [10635], "characters": "\u298B" }, + "⦏": { "codepoints": [10639], "characters": "\u298F" }, + "⦍": { "codepoints": [10637], "characters": "\u298D" }, + "ľ": { "codepoints": [318], "characters": "\u013E" }, + "ļ": { "codepoints": [316], "characters": "\u013C" }, + "⌈": { "codepoints": [8968], "characters": "\u2308" }, + "{": { "codepoints": [123], "characters": "\u007B" }, + "л": { "codepoints": [1083], "characters": "\u043B" }, + "⤶": { "codepoints": [10550], "characters": "\u2936" }, + "“": { "codepoints": [8220], "characters": "\u201C" }, + "„": { "codepoints": [8222], "characters": "\u201E" }, + "⥧": { "codepoints": [10599], "characters": "\u2967" }, + "⥋": { "codepoints": [10571], "characters": "\u294B" }, + "↲": { "codepoints": [8626], "characters": "\u21B2" }, + "≤": { "codepoints": [8804], "characters": "\u2264" }, + "←": { "codepoints": [8592], "characters": "\u2190" }, + "↢": { "codepoints": [8610], "characters": "\u21A2" }, + "↽": { "codepoints": [8637], "characters": "\u21BD" }, + "↼": { "codepoints": [8636], "characters": "\u21BC" }, + "⇇": { "codepoints": [8647], "characters": "\u21C7" }, + "↔": { "codepoints": [8596], "characters": "\u2194" }, + "⇆": { "codepoints": [8646], "characters": "\u21C6" }, + "⇋": { "codepoints": [8651], "characters": "\u21CB" }, + "↭": { "codepoints": [8621], "characters": "\u21AD" }, + "⋋": { "codepoints": [8907], "characters": "\u22CB" }, + "⋚": { "codepoints": [8922], "characters": "\u22DA" }, + "≤": { "codepoints": [8804], "characters": "\u2264" }, + "≦": { "codepoints": [8806], "characters": "\u2266" }, + "⩽": { "codepoints": [10877], "characters": "\u2A7D" }, + "⩽": { "codepoints": [10877], "characters": "\u2A7D" }, + "⪨": { "codepoints": [10920], "characters": "\u2AA8" }, + "⩿": { "codepoints": [10879], "characters": "\u2A7F" }, + "⪁": { "codepoints": [10881], "characters": "\u2A81" }, + "⪃": { "codepoints": [10883], "characters": "\u2A83" }, + "⋚︀": { "codepoints": [8922, 65024], "characters": "\u22DA\uFE00" }, + "⪓": { "codepoints": [10899], "characters": "\u2A93" }, + "⪅": { "codepoints": [10885], "characters": "\u2A85" }, + "⋖": { "codepoints": [8918], "characters": "\u22D6" }, + "⋚": { "codepoints": [8922], "characters": "\u22DA" }, + "⪋": { "codepoints": [10891], "characters": "\u2A8B" }, + "≶": { "codepoints": [8822], "characters": "\u2276" }, + "≲": { "codepoints": [8818], "characters": "\u2272" }, + "⥼": { "codepoints": [10620], "characters": "\u297C" }, + "⌊": { "codepoints": [8970], "characters": "\u230A" }, + "𝔩": { "codepoints": [120105], "characters": "\uD835\uDD29" }, + "≶": { "codepoints": [8822], "characters": "\u2276" }, + "⪑": { "codepoints": [10897], "characters": "\u2A91" }, + "↽": { "codepoints": [8637], "characters": "\u21BD" }, + "↼": { "codepoints": [8636], "characters": "\u21BC" }, + "⥪": { "codepoints": [10602], "characters": "\u296A" }, + "▄": { "codepoints": [9604], "characters": "\u2584" }, + "љ": { "codepoints": [1113], "characters": "\u0459" }, + "≪": { "codepoints": [8810], "characters": "\u226A" }, + "⇇": { "codepoints": [8647], "characters": "\u21C7" }, + "⌞": { "codepoints": [8990], "characters": "\u231E" }, + "⥫": { "codepoints": [10603], "characters": "\u296B" }, + "◺": { "codepoints": [9722], "characters": "\u25FA" }, + "ŀ": { "codepoints": [320], "characters": "\u0140" }, + "⎰": { "codepoints": [9136], "characters": "\u23B0" }, + "⎰": { "codepoints": [9136], "characters": "\u23B0" }, + "≨": { "codepoints": [8808], "characters": "\u2268" }, + "⪉": { "codepoints": [10889], "characters": "\u2A89" }, + "⪉": { "codepoints": [10889], "characters": "\u2A89" }, + "⪇": { "codepoints": [10887], "characters": "\u2A87" }, + "⪇": { "codepoints": [10887], "characters": "\u2A87" }, + "≨": { "codepoints": [8808], "characters": "\u2268" }, + "⋦": { "codepoints": [8934], "characters": "\u22E6" }, + "⟬": { "codepoints": [10220], "characters": "\u27EC" }, + "⇽": { "codepoints": [8701], "characters": "\u21FD" }, + "⟦": { "codepoints": [10214], "characters": "\u27E6" }, + "⟵": { "codepoints": [10229], "characters": "\u27F5" }, + "⟷": { "codepoints": [10231], "characters": "\u27F7" }, + "⟼": { "codepoints": [10236], "characters": "\u27FC" }, + "⟶": { "codepoints": [10230], "characters": "\u27F6" }, + "↫": { "codepoints": [8619], "characters": "\u21AB" }, + "↬": { "codepoints": [8620], "characters": "\u21AC" }, + "⦅": { "codepoints": [10629], "characters": "\u2985" }, + "𝕝": { "codepoints": [120157], "characters": "\uD835\uDD5D" }, + "⨭": { "codepoints": [10797], "characters": "\u2A2D" }, + "⨴": { "codepoints": [10804], "characters": "\u2A34" }, + "∗": { "codepoints": [8727], "characters": "\u2217" }, + "_": { "codepoints": [95], "characters": "\u005F" }, + "◊": { "codepoints": [9674], "characters": "\u25CA" }, + "◊": { "codepoints": [9674], "characters": "\u25CA" }, + "⧫": { "codepoints": [10731], "characters": "\u29EB" }, + "(": { "codepoints": [40], "characters": "\u0028" }, + "⦓": { "codepoints": [10643], "characters": "\u2993" }, + "⇆": { "codepoints": [8646], "characters": "\u21C6" }, + "⌟": { "codepoints": [8991], "characters": "\u231F" }, + "⇋": { "codepoints": [8651], "characters": "\u21CB" }, + "⥭": { "codepoints": [10605], "characters": "\u296D" }, + "‎": { "codepoints": [8206], "characters": "\u200E" }, + "⊿": { "codepoints": [8895], "characters": "\u22BF" }, + "‹": { "codepoints": [8249], "characters": "\u2039" }, + "𝓁": { "codepoints": [120001], "characters": "\uD835\uDCC1" }, + "↰": { "codepoints": [8624], "characters": "\u21B0" }, + "≲": { "codepoints": [8818], "characters": "\u2272" }, + "⪍": { "codepoints": [10893], "characters": "\u2A8D" }, + "⪏": { "codepoints": [10895], "characters": "\u2A8F" }, + "[": { "codepoints": [91], "characters": "\u005B" }, + "‘": { "codepoints": [8216], "characters": "\u2018" }, + "‚": { "codepoints": [8218], "characters": "\u201A" }, + "ł": { "codepoints": [322], "characters": "\u0142" }, + "<": { "codepoints": [60], "characters": "\u003C" }, + "<": { "codepoints": [60], "characters": "\u003C" }, + "⪦": { "codepoints": [10918], "characters": "\u2AA6" }, + "⩹": { "codepoints": [10873], "characters": "\u2A79" }, + "⋖": { "codepoints": [8918], "characters": "\u22D6" }, + "⋋": { "codepoints": [8907], "characters": "\u22CB" }, + "⋉": { "codepoints": [8905], "characters": "\u22C9" }, + "⥶": { "codepoints": [10614], "characters": "\u2976" }, + "⩻": { "codepoints": [10875], "characters": "\u2A7B" }, + "⦖": { "codepoints": [10646], "characters": "\u2996" }, + "◃": { "codepoints": [9667], "characters": "\u25C3" }, + "⊴": { "codepoints": [8884], "characters": "\u22B4" }, + "◂": { "codepoints": [9666], "characters": "\u25C2" }, + "⥊": { "codepoints": [10570], "characters": "\u294A" }, + "⥦": { "codepoints": [10598], "characters": "\u2966" }, + "≨︀": { "codepoints": [8808, 65024], "characters": "\u2268\uFE00" }, + "≨︀": { "codepoints": [8808, 65024], "characters": "\u2268\uFE00" }, + "∺": { "codepoints": [8762], "characters": "\u223A" }, + "¯": { "codepoints": [175], "characters": "\u00AF" }, + "¯": { "codepoints": [175], "characters": "\u00AF" }, + "♂": { "codepoints": [9794], "characters": "\u2642" }, + "✠": { "codepoints": [10016], "characters": "\u2720" }, + "✠": { "codepoints": [10016], "characters": "\u2720" }, + "↦": { "codepoints": [8614], "characters": "\u21A6" }, + "↦": { "codepoints": [8614], "characters": "\u21A6" }, + "↧": { "codepoints": [8615], "characters": "\u21A7" }, + "↤": { "codepoints": [8612], "characters": "\u21A4" }, + "↥": { "codepoints": [8613], "characters": "\u21A5" }, + "▮": { "codepoints": [9646], "characters": "\u25AE" }, + "⨩": { "codepoints": [10793], "characters": "\u2A29" }, + "м": { "codepoints": [1084], "characters": "\u043C" }, + "—": { "codepoints": [8212], "characters": "\u2014" }, + "∡": { "codepoints": [8737], "characters": "\u2221" }, + "𝔪": { "codepoints": [120106], "characters": "\uD835\uDD2A" }, + "℧": { "codepoints": [8487], "characters": "\u2127" }, + "µ": { "codepoints": [181], "characters": "\u00B5" }, + "µ": { "codepoints": [181], "characters": "\u00B5" }, + "∣": { "codepoints": [8739], "characters": "\u2223" }, + "*": { "codepoints": [42], "characters": "\u002A" }, + "⫰": { "codepoints": [10992], "characters": "\u2AF0" }, + "·": { "codepoints": [183], "characters": "\u00B7" }, + "·": { "codepoints": [183], "characters": "\u00B7" }, + "−": { "codepoints": [8722], "characters": "\u2212" }, + "⊟": { "codepoints": [8863], "characters": "\u229F" }, + "∸": { "codepoints": [8760], "characters": "\u2238" }, + "⨪": { "codepoints": [10794], "characters": "\u2A2A" }, + "⫛": { "codepoints": [10971], "characters": "\u2ADB" }, + "…": { "codepoints": [8230], "characters": "\u2026" }, + "∓": { "codepoints": [8723], "characters": "\u2213" }, + "⊧": { "codepoints": [8871], "characters": "\u22A7" }, + "𝕞": { "codepoints": [120158], "characters": "\uD835\uDD5E" }, + "∓": { "codepoints": [8723], "characters": "\u2213" }, + "𝓂": { "codepoints": [120002], "characters": "\uD835\uDCC2" }, + "∾": { "codepoints": [8766], "characters": "\u223E" }, + "μ": { "codepoints": [956], "characters": "\u03BC" }, + "⊸": { "codepoints": [8888], "characters": "\u22B8" }, + "⊸": { "codepoints": [8888], "characters": "\u22B8" }, + "⋙̸": { "codepoints": [8921, 824], "characters": "\u22D9\u0338" }, + "≫⃒": { "codepoints": [8811, 8402], "characters": "\u226B\u20D2" }, + "≫̸": { "codepoints": [8811, 824], "characters": "\u226B\u0338" }, + "⇍": { "codepoints": [8653], "characters": "\u21CD" }, + "⇎": { "codepoints": [8654], "characters": "\u21CE" }, + "⋘̸": { "codepoints": [8920, 824], "characters": "\u22D8\u0338" }, + "≪⃒": { "codepoints": [8810, 8402], "characters": "\u226A\u20D2" }, + "≪̸": { "codepoints": [8810, 824], "characters": "\u226A\u0338" }, + "⇏": { "codepoints": [8655], "characters": "\u21CF" }, + "⊯": { "codepoints": [8879], "characters": "\u22AF" }, + "⊮": { "codepoints": [8878], "characters": "\u22AE" }, + "∇": { "codepoints": [8711], "characters": "\u2207" }, + "ń": { "codepoints": [324], "characters": "\u0144" }, + "∠⃒": { "codepoints": [8736, 8402], "characters": "\u2220\u20D2" }, + "≉": { "codepoints": [8777], "characters": "\u2249" }, + "⩰̸": { "codepoints": [10864, 824], "characters": "\u2A70\u0338" }, + "≋̸": { "codepoints": [8779, 824], "characters": "\u224B\u0338" }, + "ʼn": { "codepoints": [329], "characters": "\u0149" }, + "≉": { "codepoints": [8777], "characters": "\u2249" }, + "♮": { "codepoints": [9838], "characters": "\u266E" }, + "♮": { "codepoints": [9838], "characters": "\u266E" }, + "ℕ": { "codepoints": [8469], "characters": "\u2115" }, + " ": { "codepoints": [160], "characters": "\u00A0" }, + " ": { "codepoints": [160], "characters": "\u00A0" }, + "≎̸": { "codepoints": [8782, 824], "characters": "\u224E\u0338" }, + "≏̸": { "codepoints": [8783, 824], "characters": "\u224F\u0338" }, + "⩃": { "codepoints": [10819], "characters": "\u2A43" }, + "ň": { "codepoints": [328], "characters": "\u0148" }, + "ņ": { "codepoints": [326], "characters": "\u0146" }, + "≇": { "codepoints": [8775], "characters": "\u2247" }, + "⩭̸": { "codepoints": [10861, 824], "characters": "\u2A6D\u0338" }, + "⩂": { "codepoints": [10818], "characters": "\u2A42" }, + "н": { "codepoints": [1085], "characters": "\u043D" }, + "–": { "codepoints": [8211], "characters": "\u2013" }, + "≠": { "codepoints": [8800], "characters": "\u2260" }, + "⇗": { "codepoints": [8663], "characters": "\u21D7" }, + "⤤": { "codepoints": [10532], "characters": "\u2924" }, + "↗": { "codepoints": [8599], "characters": "\u2197" }, + "↗": { "codepoints": [8599], "characters": "\u2197" }, + "≐̸": { "codepoints": [8784, 824], "characters": "\u2250\u0338" }, + "≢": { "codepoints": [8802], "characters": "\u2262" }, + "⤨": { "codepoints": [10536], "characters": "\u2928" }, + "≂̸": { "codepoints": [8770, 824], "characters": "\u2242\u0338" }, + "∄": { "codepoints": [8708], "characters": "\u2204" }, + "∄": { "codepoints": [8708], "characters": "\u2204" }, + "𝔫": { "codepoints": [120107], "characters": "\uD835\uDD2B" }, + "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" }, + "≱": { "codepoints": [8817], "characters": "\u2271" }, + "≱": { "codepoints": [8817], "characters": "\u2271" }, + "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" }, + "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" }, + "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" }, + "≵": { "codepoints": [8821], "characters": "\u2275" }, + "≯": { "codepoints": [8815], "characters": "\u226F" }, + "≯": { "codepoints": [8815], "characters": "\u226F" }, + "⇎": { "codepoints": [8654], "characters": "\u21CE" }, + "↮": { "codepoints": [8622], "characters": "\u21AE" }, + "⫲": { "codepoints": [10994], "characters": "\u2AF2" }, + "∋": { "codepoints": [8715], "characters": "\u220B" }, + "⋼": { "codepoints": [8956], "characters": "\u22FC" }, + "⋺": { "codepoints": [8954], "characters": "\u22FA" }, + "∋": { "codepoints": [8715], "characters": "\u220B" }, + "њ": { "codepoints": [1114], "characters": "\u045A" }, + "⇍": { "codepoints": [8653], "characters": "\u21CD" }, + "≦̸": { "codepoints": [8806, 824], "characters": "\u2266\u0338" }, + "↚": { "codepoints": [8602], "characters": "\u219A" }, + "‥": { "codepoints": [8229], "characters": "\u2025" }, + "≰": { "codepoints": [8816], "characters": "\u2270" }, + "↚": { "codepoints": [8602], "characters": "\u219A" }, + "↮": { "codepoints": [8622], "characters": "\u21AE" }, + "≰": { "codepoints": [8816], "characters": "\u2270" }, + "≦̸": { "codepoints": [8806, 824], "characters": "\u2266\u0338" }, + "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" }, + "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" }, + "≮": { "codepoints": [8814], "characters": "\u226E" }, + "≴": { "codepoints": [8820], "characters": "\u2274" }, + "≮": { "codepoints": [8814], "characters": "\u226E" }, + "⋪": { "codepoints": [8938], "characters": "\u22EA" }, + "⋬": { "codepoints": [8940], "characters": "\u22EC" }, + "∤": { "codepoints": [8740], "characters": "\u2224" }, + "𝕟": { "codepoints": [120159], "characters": "\uD835\uDD5F" }, + "¬": { "codepoints": [172], "characters": "\u00AC" }, + "¬": { "codepoints": [172], "characters": "\u00AC" }, + "∉": { "codepoints": [8713], "characters": "\u2209" }, + "⋹̸": { "codepoints": [8953, 824], "characters": "\u22F9\u0338" }, + "⋵̸": { "codepoints": [8949, 824], "characters": "\u22F5\u0338" }, + "∉": { "codepoints": [8713], "characters": "\u2209" }, + "⋷": { "codepoints": [8951], "characters": "\u22F7" }, + "⋶": { "codepoints": [8950], "characters": "\u22F6" }, + "∌": { "codepoints": [8716], "characters": "\u220C" }, + "∌": { "codepoints": [8716], "characters": "\u220C" }, + "⋾": { "codepoints": [8958], "characters": "\u22FE" }, + "⋽": { "codepoints": [8957], "characters": "\u22FD" }, + "∦": { "codepoints": [8742], "characters": "\u2226" }, + "∦": { "codepoints": [8742], "characters": "\u2226" }, + "⫽⃥": { "codepoints": [11005, 8421], "characters": "\u2AFD\u20E5" }, + "∂̸": { "codepoints": [8706, 824], "characters": "\u2202\u0338" }, + "⨔": { "codepoints": [10772], "characters": "\u2A14" }, + "⊀": { "codepoints": [8832], "characters": "\u2280" }, + "⋠": { "codepoints": [8928], "characters": "\u22E0" }, + "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" }, + "⊀": { "codepoints": [8832], "characters": "\u2280" }, + "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" }, + "⇏": { "codepoints": [8655], "characters": "\u21CF" }, + "↛": { "codepoints": [8603], "characters": "\u219B" }, + "⤳̸": { "codepoints": [10547, 824], "characters": "\u2933\u0338" }, + "↝̸": { "codepoints": [8605, 824], "characters": "\u219D\u0338" }, + "↛": { "codepoints": [8603], "characters": "\u219B" }, + "⋫": { "codepoints": [8939], "characters": "\u22EB" }, + "⋭": { "codepoints": [8941], "characters": "\u22ED" }, + "⊁": { "codepoints": [8833], "characters": "\u2281" }, + "⋡": { "codepoints": [8929], "characters": "\u22E1" }, + "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" }, + "𝓃": { "codepoints": [120003], "characters": "\uD835\uDCC3" }, + "∤": { "codepoints": [8740], "characters": "\u2224" }, + "∦": { "codepoints": [8742], "characters": "\u2226" }, + "≁": { "codepoints": [8769], "characters": "\u2241" }, + "≄": { "codepoints": [8772], "characters": "\u2244" }, + "≄": { "codepoints": [8772], "characters": "\u2244" }, + "∤": { "codepoints": [8740], "characters": "\u2224" }, + "∦": { "codepoints": [8742], "characters": "\u2226" }, + "⋢": { "codepoints": [8930], "characters": "\u22E2" }, + "⋣": { "codepoints": [8931], "characters": "\u22E3" }, + "⊄": { "codepoints": [8836], "characters": "\u2284" }, + "⫅̸": { "codepoints": [10949, 824], "characters": "\u2AC5\u0338" }, + "⊈": { "codepoints": [8840], "characters": "\u2288" }, + "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" }, + "⊈": { "codepoints": [8840], "characters": "\u2288" }, + "⫅̸": { "codepoints": [10949, 824], "characters": "\u2AC5\u0338" }, + "⊁": { "codepoints": [8833], "characters": "\u2281" }, + "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" }, + "⊅": { "codepoints": [8837], "characters": "\u2285" }, + "⫆̸": { "codepoints": [10950, 824], "characters": "\u2AC6\u0338" }, + "⊉": { "codepoints": [8841], "characters": "\u2289" }, + "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" }, + "⊉": { "codepoints": [8841], "characters": "\u2289" }, + "⫆̸": { "codepoints": [10950, 824], "characters": "\u2AC6\u0338" }, + "≹": { "codepoints": [8825], "characters": "\u2279" }, + "ñ": { "codepoints": [241], "characters": "\u00F1" }, + "ñ": { "codepoints": [241], "characters": "\u00F1" }, + "≸": { "codepoints": [8824], "characters": "\u2278" }, + "⋪": { "codepoints": [8938], "characters": "\u22EA" }, + "⋬": { "codepoints": [8940], "characters": "\u22EC" }, + "⋫": { "codepoints": [8939], "characters": "\u22EB" }, + "⋭": { "codepoints": [8941], "characters": "\u22ED" }, + "ν": { "codepoints": [957], "characters": "\u03BD" }, + "#": { "codepoints": [35], "characters": "\u0023" }, + "№": { "codepoints": [8470], "characters": "\u2116" }, + " ": { "codepoints": [8199], "characters": "\u2007" }, + "⊭": { "codepoints": [8877], "characters": "\u22AD" }, + "⤄": { "codepoints": [10500], "characters": "\u2904" }, + "≍⃒": { "codepoints": [8781, 8402], "characters": "\u224D\u20D2" }, + "⊬": { "codepoints": [8876], "characters": "\u22AC" }, + "≥⃒": { "codepoints": [8805, 8402], "characters": "\u2265\u20D2" }, + ">⃒": { "codepoints": [62, 8402], "characters": "\u003E\u20D2" }, + "⧞": { "codepoints": [10718], "characters": "\u29DE" }, + "⤂": { "codepoints": [10498], "characters": "\u2902" }, + "≤⃒": { "codepoints": [8804, 8402], "characters": "\u2264\u20D2" }, + "<⃒": { "codepoints": [60, 8402], "characters": "\u003C\u20D2" }, + "⊴⃒": { "codepoints": [8884, 8402], "characters": "\u22B4\u20D2" }, + "⤃": { "codepoints": [10499], "characters": "\u2903" }, + "⊵⃒": { "codepoints": [8885, 8402], "characters": "\u22B5\u20D2" }, + "∼⃒": { "codepoints": [8764, 8402], "characters": "\u223C\u20D2" }, + "⇖": { "codepoints": [8662], "characters": "\u21D6" }, + "⤣": { "codepoints": [10531], "characters": "\u2923" }, + "↖": { "codepoints": [8598], "characters": "\u2196" }, + "↖": { "codepoints": [8598], "characters": "\u2196" }, + "⤧": { "codepoints": [10535], "characters": "\u2927" }, + "Ⓢ": { "codepoints": [9416], "characters": "\u24C8" }, + "ó": { "codepoints": [243], "characters": "\u00F3" }, + "ó": { "codepoints": [243], "characters": "\u00F3" }, + "⊛": { "codepoints": [8859], "characters": "\u229B" }, + "⊚": { "codepoints": [8858], "characters": "\u229A" }, + "ô": { "codepoints": [244], "characters": "\u00F4" }, + "ô": { "codepoints": [244], "characters": "\u00F4" }, + "о": { "codepoints": [1086], "characters": "\u043E" }, + "⊝": { "codepoints": [8861], "characters": "\u229D" }, + "ő": { "codepoints": [337], "characters": "\u0151" }, + "⨸": { "codepoints": [10808], "characters": "\u2A38" }, + "⊙": { "codepoints": [8857], "characters": "\u2299" }, + "⦼": { "codepoints": [10684], "characters": "\u29BC" }, + "œ": { "codepoints": [339], "characters": "\u0153" }, + "⦿": { "codepoints": [10687], "characters": "\u29BF" }, + "𝔬": { "codepoints": [120108], "characters": "\uD835\uDD2C" }, + "˛": { "codepoints": [731], "characters": "\u02DB" }, + "ò": { "codepoints": [242], "characters": "\u00F2" }, + "ò": { "codepoints": [242], "characters": "\u00F2" }, + "⧁": { "codepoints": [10689], "characters": "\u29C1" }, + "⦵": { "codepoints": [10677], "characters": "\u29B5" }, + "Ω": { "codepoints": [937], "characters": "\u03A9" }, + "∮": { "codepoints": [8750], "characters": "\u222E" }, + "↺": { "codepoints": [8634], "characters": "\u21BA" }, + "⦾": { "codepoints": [10686], "characters": "\u29BE" }, + "⦻": { "codepoints": [10683], "characters": "\u29BB" }, + "‾": { "codepoints": [8254], "characters": "\u203E" }, + "⧀": { "codepoints": [10688], "characters": "\u29C0" }, + "ō": { "codepoints": [333], "characters": "\u014D" }, + "ω": { "codepoints": [969], "characters": "\u03C9" }, + "ο": { "codepoints": [959], "characters": "\u03BF" }, + "⦶": { "codepoints": [10678], "characters": "\u29B6" }, + "⊖": { "codepoints": [8854], "characters": "\u2296" }, + "𝕠": { "codepoints": [120160], "characters": "\uD835\uDD60" }, + "⦷": { "codepoints": [10679], "characters": "\u29B7" }, + "⦹": { "codepoints": [10681], "characters": "\u29B9" }, + "⊕": { "codepoints": [8853], "characters": "\u2295" }, + "∨": { "codepoints": [8744], "characters": "\u2228" }, + "↻": { "codepoints": [8635], "characters": "\u21BB" }, + "⩝": { "codepoints": [10845], "characters": "\u2A5D" }, + "ℴ": { "codepoints": [8500], "characters": "\u2134" }, + "ℴ": { "codepoints": [8500], "characters": "\u2134" }, + "ª": { "codepoints": [170], "characters": "\u00AA" }, + "ª": { "codepoints": [170], "characters": "\u00AA" }, + "º": { "codepoints": [186], "characters": "\u00BA" }, + "º": { "codepoints": [186], "characters": "\u00BA" }, + "⊶": { "codepoints": [8886], "characters": "\u22B6" }, + "⩖": { "codepoints": [10838], "characters": "\u2A56" }, + "⩗": { "codepoints": [10839], "characters": "\u2A57" }, + "⩛": { "codepoints": [10843], "characters": "\u2A5B" }, + "ℴ": { "codepoints": [8500], "characters": "\u2134" }, + "ø": { "codepoints": [248], "characters": "\u00F8" }, + "ø": { "codepoints": [248], "characters": "\u00F8" }, + "⊘": { "codepoints": [8856], "characters": "\u2298" }, + "õ": { "codepoints": [245], "characters": "\u00F5" }, + "õ": { "codepoints": [245], "characters": "\u00F5" }, + "⊗": { "codepoints": [8855], "characters": "\u2297" }, + "⨶": { "codepoints": [10806], "characters": "\u2A36" }, + "ö": { "codepoints": [246], "characters": "\u00F6" }, + "ö": { "codepoints": [246], "characters": "\u00F6" }, + "⌽": { "codepoints": [9021], "characters": "\u233D" }, + "∥": { "codepoints": [8741], "characters": "\u2225" }, + "¶": { "codepoints": [182], "characters": "\u00B6" }, + "¶": { "codepoints": [182], "characters": "\u00B6" }, + "∥": { "codepoints": [8741], "characters": "\u2225" }, + "⫳": { "codepoints": [10995], "characters": "\u2AF3" }, + "⫽": { "codepoints": [11005], "characters": "\u2AFD" }, + "∂": { "codepoints": [8706], "characters": "\u2202" }, + "п": { "codepoints": [1087], "characters": "\u043F" }, + "%": { "codepoints": [37], "characters": "\u0025" }, + ".": { "codepoints": [46], "characters": "\u002E" }, + "‰": { "codepoints": [8240], "characters": "\u2030" }, + "⊥": { "codepoints": [8869], "characters": "\u22A5" }, + "‱": { "codepoints": [8241], "characters": "\u2031" }, + "𝔭": { "codepoints": [120109], "characters": "\uD835\uDD2D" }, + "φ": { "codepoints": [966], "characters": "\u03C6" }, + "ϕ": { "codepoints": [981], "characters": "\u03D5" }, + "ℳ": { "codepoints": [8499], "characters": "\u2133" }, + "☎": { "codepoints": [9742], "characters": "\u260E" }, + "π": { "codepoints": [960], "characters": "\u03C0" }, + "⋔": { "codepoints": [8916], "characters": "\u22D4" }, + "ϖ": { "codepoints": [982], "characters": "\u03D6" }, + "ℏ": { "codepoints": [8463], "characters": "\u210F" }, + "ℎ": { "codepoints": [8462], "characters": "\u210E" }, + "ℏ": { "codepoints": [8463], "characters": "\u210F" }, + "+": { "codepoints": [43], "characters": "\u002B" }, + "⨣": { "codepoints": [10787], "characters": "\u2A23" }, + "⊞": { "codepoints": [8862], "characters": "\u229E" }, + "⨢": { "codepoints": [10786], "characters": "\u2A22" }, + "∔": { "codepoints": [8724], "characters": "\u2214" }, + "⨥": { "codepoints": [10789], "characters": "\u2A25" }, + "⩲": { "codepoints": [10866], "characters": "\u2A72" }, + "±": { "codepoints": [177], "characters": "\u00B1" }, + "±": { "codepoints": [177], "characters": "\u00B1" }, + "⨦": { "codepoints": [10790], "characters": "\u2A26" }, + "⨧": { "codepoints": [10791], "characters": "\u2A27" }, + "±": { "codepoints": [177], "characters": "\u00B1" }, + "⨕": { "codepoints": [10773], "characters": "\u2A15" }, + "𝕡": { "codepoints": [120161], "characters": "\uD835\uDD61" }, + "£": { "codepoints": [163], "characters": "\u00A3" }, + "£": { "codepoints": [163], "characters": "\u00A3" }, + "≺": { "codepoints": [8826], "characters": "\u227A" }, + "⪳": { "codepoints": [10931], "characters": "\u2AB3" }, + "⪷": { "codepoints": [10935], "characters": "\u2AB7" }, + "≼": { "codepoints": [8828], "characters": "\u227C" }, + "⪯": { "codepoints": [10927], "characters": "\u2AAF" }, + "≺": { "codepoints": [8826], "characters": "\u227A" }, + "⪷": { "codepoints": [10935], "characters": "\u2AB7" }, + "≼": { "codepoints": [8828], "characters": "\u227C" }, + "⪯": { "codepoints": [10927], "characters": "\u2AAF" }, + "⪹": { "codepoints": [10937], "characters": "\u2AB9" }, + "⪵": { "codepoints": [10933], "characters": "\u2AB5" }, + "⋨": { "codepoints": [8936], "characters": "\u22E8" }, + "≾": { "codepoints": [8830], "characters": "\u227E" }, + "′": { "codepoints": [8242], "characters": "\u2032" }, + "ℙ": { "codepoints": [8473], "characters": "\u2119" }, + "⪵": { "codepoints": [10933], "characters": "\u2AB5" }, + "⪹": { "codepoints": [10937], "characters": "\u2AB9" }, + "⋨": { "codepoints": [8936], "characters": "\u22E8" }, + "∏": { "codepoints": [8719], "characters": "\u220F" }, + "⌮": { "codepoints": [9006], "characters": "\u232E" }, + "⌒": { "codepoints": [8978], "characters": "\u2312" }, + "⌓": { "codepoints": [8979], "characters": "\u2313" }, + "∝": { "codepoints": [8733], "characters": "\u221D" }, + "∝": { "codepoints": [8733], "characters": "\u221D" }, + "≾": { "codepoints": [8830], "characters": "\u227E" }, + "⊰": { "codepoints": [8880], "characters": "\u22B0" }, + "𝓅": { "codepoints": [120005], "characters": "\uD835\uDCC5" }, + "ψ": { "codepoints": [968], "characters": "\u03C8" }, + " ": { "codepoints": [8200], "characters": "\u2008" }, + "𝔮": { "codepoints": [120110], "characters": "\uD835\uDD2E" }, + "⨌": { "codepoints": [10764], "characters": "\u2A0C" }, + "𝕢": { "codepoints": [120162], "characters": "\uD835\uDD62" }, + "⁗": { "codepoints": [8279], "characters": "\u2057" }, + "𝓆": { "codepoints": [120006], "characters": "\uD835\uDCC6" }, + "ℍ": { "codepoints": [8461], "characters": "\u210D" }, + "⨖": { "codepoints": [10774], "characters": "\u2A16" }, + "?": { "codepoints": [63], "characters": "\u003F" }, + "≟": { "codepoints": [8799], "characters": "\u225F" }, + """: { "codepoints": [34], "characters": "\u0022" }, + """: { "codepoints": [34], "characters": "\u0022" }, + "⇛": { "codepoints": [8667], "characters": "\u21DB" }, + "⇒": { "codepoints": [8658], "characters": "\u21D2" }, + "⤜": { "codepoints": [10524], "characters": "\u291C" }, + "⤏": { "codepoints": [10511], "characters": "\u290F" }, + "⥤": { "codepoints": [10596], "characters": "\u2964" }, + "∽̱": { "codepoints": [8765, 817], "characters": "\u223D\u0331" }, + "ŕ": { "codepoints": [341], "characters": "\u0155" }, + "√": { "codepoints": [8730], "characters": "\u221A" }, + "⦳": { "codepoints": [10675], "characters": "\u29B3" }, + "⟩": { "codepoints": [10217], "characters": "\u27E9" }, + "⦒": { "codepoints": [10642], "characters": "\u2992" }, + "⦥": { "codepoints": [10661], "characters": "\u29A5" }, + "⟩": { "codepoints": [10217], "characters": "\u27E9" }, + "»": { "codepoints": [187], "characters": "\u00BB" }, + "»": { "codepoints": [187], "characters": "\u00BB" }, + "→": { "codepoints": [8594], "characters": "\u2192" }, + "⥵": { "codepoints": [10613], "characters": "\u2975" }, + "⇥": { "codepoints": [8677], "characters": "\u21E5" }, + "⤠": { "codepoints": [10528], "characters": "\u2920" }, + "⤳": { "codepoints": [10547], "characters": "\u2933" }, + "⤞": { "codepoints": [10526], "characters": "\u291E" }, + "↪": { "codepoints": [8618], "characters": "\u21AA" }, + "↬": { "codepoints": [8620], "characters": "\u21AC" }, + "⥅": { "codepoints": [10565], "characters": "\u2945" }, + "⥴": { "codepoints": [10612], "characters": "\u2974" }, + "↣": { "codepoints": [8611], "characters": "\u21A3" }, + "↝": { "codepoints": [8605], "characters": "\u219D" }, + "⤚": { "codepoints": [10522], "characters": "\u291A" }, + "∶": { "codepoints": [8758], "characters": "\u2236" }, + "ℚ": { "codepoints": [8474], "characters": "\u211A" }, + "⤍": { "codepoints": [10509], "characters": "\u290D" }, + "❳": { "codepoints": [10099], "characters": "\u2773" }, + "}": { "codepoints": [125], "characters": "\u007D" }, + "]": { "codepoints": [93], "characters": "\u005D" }, + "⦌": { "codepoints": [10636], "characters": "\u298C" }, + "⦎": { "codepoints": [10638], "characters": "\u298E" }, + "⦐": { "codepoints": [10640], "characters": "\u2990" }, + "ř": { "codepoints": [345], "characters": "\u0159" }, + "ŗ": { "codepoints": [343], "characters": "\u0157" }, + "⌉": { "codepoints": [8969], "characters": "\u2309" }, + "}": { "codepoints": [125], "characters": "\u007D" }, + "р": { "codepoints": [1088], "characters": "\u0440" }, + "⤷": { "codepoints": [10551], "characters": "\u2937" }, + "⥩": { "codepoints": [10601], "characters": "\u2969" }, + "”": { "codepoints": [8221], "characters": "\u201D" }, + "”": { "codepoints": [8221], "characters": "\u201D" }, + "↳": { "codepoints": [8627], "characters": "\u21B3" }, + "ℜ": { "codepoints": [8476], "characters": "\u211C" }, + "ℛ": { "codepoints": [8475], "characters": "\u211B" }, + "ℜ": { "codepoints": [8476], "characters": "\u211C" }, + "ℝ": { "codepoints": [8477], "characters": "\u211D" }, + "▭": { "codepoints": [9645], "characters": "\u25AD" }, + "®": { "codepoints": [174], "characters": "\u00AE" }, + "®": { "codepoints": [174], "characters": "\u00AE" }, + "⥽": { "codepoints": [10621], "characters": "\u297D" }, + "⌋": { "codepoints": [8971], "characters": "\u230B" }, + "𝔯": { "codepoints": [120111], "characters": "\uD835\uDD2F" }, + "⇁": { "codepoints": [8641], "characters": "\u21C1" }, + "⇀": { "codepoints": [8640], "characters": "\u21C0" }, + "⥬": { "codepoints": [10604], "characters": "\u296C" }, + "ρ": { "codepoints": [961], "characters": "\u03C1" }, + "ϱ": { "codepoints": [1009], "characters": "\u03F1" }, + "→": { "codepoints": [8594], "characters": "\u2192" }, + "↣": { "codepoints": [8611], "characters": "\u21A3" }, + "⇁": { "codepoints": [8641], "characters": "\u21C1" }, + "⇀": { "codepoints": [8640], "characters": "\u21C0" }, + "⇄": { "codepoints": [8644], "characters": "\u21C4" }, + "⇌": { "codepoints": [8652], "characters": "\u21CC" }, + "⇉": { "codepoints": [8649], "characters": "\u21C9" }, + "↝": { "codepoints": [8605], "characters": "\u219D" }, + "⋌": { "codepoints": [8908], "characters": "\u22CC" }, + "˚": { "codepoints": [730], "characters": "\u02DA" }, + "≓": { "codepoints": [8787], "characters": "\u2253" }, + "⇄": { "codepoints": [8644], "characters": "\u21C4" }, + "⇌": { "codepoints": [8652], "characters": "\u21CC" }, + "‏": { "codepoints": [8207], "characters": "\u200F" }, + "⎱": { "codepoints": [9137], "characters": "\u23B1" }, + "⎱": { "codepoints": [9137], "characters": "\u23B1" }, + "⫮": { "codepoints": [10990], "characters": "\u2AEE" }, + "⟭": { "codepoints": [10221], "characters": "\u27ED" }, + "⇾": { "codepoints": [8702], "characters": "\u21FE" }, + "⟧": { "codepoints": [10215], "characters": "\u27E7" }, + "⦆": { "codepoints": [10630], "characters": "\u2986" }, + "𝕣": { "codepoints": [120163], "characters": "\uD835\uDD63" }, + "⨮": { "codepoints": [10798], "characters": "\u2A2E" }, + "⨵": { "codepoints": [10805], "characters": "\u2A35" }, + ")": { "codepoints": [41], "characters": "\u0029" }, + "⦔": { "codepoints": [10644], "characters": "\u2994" }, + "⨒": { "codepoints": [10770], "characters": "\u2A12" }, + "⇉": { "codepoints": [8649], "characters": "\u21C9" }, + "›": { "codepoints": [8250], "characters": "\u203A" }, + "𝓇": { "codepoints": [120007], "characters": "\uD835\uDCC7" }, + "↱": { "codepoints": [8625], "characters": "\u21B1" }, + "]": { "codepoints": [93], "characters": "\u005D" }, + "’": { "codepoints": [8217], "characters": "\u2019" }, + "’": { "codepoints": [8217], "characters": "\u2019" }, + "⋌": { "codepoints": [8908], "characters": "\u22CC" }, + "⋊": { "codepoints": [8906], "characters": "\u22CA" }, + "▹": { "codepoints": [9657], "characters": "\u25B9" }, + "⊵": { "codepoints": [8885], "characters": "\u22B5" }, + "▸": { "codepoints": [9656], "characters": "\u25B8" }, + "⧎": { "codepoints": [10702], "characters": "\u29CE" }, + "⥨": { "codepoints": [10600], "characters": "\u2968" }, + "℞": { "codepoints": [8478], "characters": "\u211E" }, + "ś": { "codepoints": [347], "characters": "\u015B" }, + "‚": { "codepoints": [8218], "characters": "\u201A" }, + "≻": { "codepoints": [8827], "characters": "\u227B" }, + "⪴": { "codepoints": [10932], "characters": "\u2AB4" }, + "⪸": { "codepoints": [10936], "characters": "\u2AB8" }, + "š": { "codepoints": [353], "characters": "\u0161" }, + "≽": { "codepoints": [8829], "characters": "\u227D" }, + "⪰": { "codepoints": [10928], "characters": "\u2AB0" }, + "ş": { "codepoints": [351], "characters": "\u015F" }, + "ŝ": { "codepoints": [349], "characters": "\u015D" }, + "⪶": { "codepoints": [10934], "characters": "\u2AB6" }, + "⪺": { "codepoints": [10938], "characters": "\u2ABA" }, + "⋩": { "codepoints": [8937], "characters": "\u22E9" }, + "⨓": { "codepoints": [10771], "characters": "\u2A13" }, + "≿": { "codepoints": [8831], "characters": "\u227F" }, + "с": { "codepoints": [1089], "characters": "\u0441" }, + "⋅": { "codepoints": [8901], "characters": "\u22C5" }, + "⊡": { "codepoints": [8865], "characters": "\u22A1" }, + "⩦": { "codepoints": [10854], "characters": "\u2A66" }, + "⇘": { "codepoints": [8664], "characters": "\u21D8" }, + "⤥": { "codepoints": [10533], "characters": "\u2925" }, + "↘": { "codepoints": [8600], "characters": "\u2198" }, + "↘": { "codepoints": [8600], "characters": "\u2198" }, + "§": { "codepoints": [167], "characters": "\u00A7" }, + "§": { "codepoints": [167], "characters": "\u00A7" }, + ";": { "codepoints": [59], "characters": "\u003B" }, + "⤩": { "codepoints": [10537], "characters": "\u2929" }, + "∖": { "codepoints": [8726], "characters": "\u2216" }, + "∖": { "codepoints": [8726], "characters": "\u2216" }, + "✶": { "codepoints": [10038], "characters": "\u2736" }, + "𝔰": { "codepoints": [120112], "characters": "\uD835\uDD30" }, + "⌢": { "codepoints": [8994], "characters": "\u2322" }, + "♯": { "codepoints": [9839], "characters": "\u266F" }, + "щ": { "codepoints": [1097], "characters": "\u0449" }, + "ш": { "codepoints": [1096], "characters": "\u0448" }, + "∣": { "codepoints": [8739], "characters": "\u2223" }, + "∥": { "codepoints": [8741], "characters": "\u2225" }, + "­": { "codepoints": [173], "characters": "\u00AD" }, + "­": { "codepoints": [173], "characters": "\u00AD" }, + "σ": { "codepoints": [963], "characters": "\u03C3" }, + "ς": { "codepoints": [962], "characters": "\u03C2" }, + "ς": { "codepoints": [962], "characters": "\u03C2" }, + "∼": { "codepoints": [8764], "characters": "\u223C" }, + "⩪": { "codepoints": [10858], "characters": "\u2A6A" }, + "≃": { "codepoints": [8771], "characters": "\u2243" }, + "≃": { "codepoints": [8771], "characters": "\u2243" }, + "⪞": { "codepoints": [10910], "characters": "\u2A9E" }, + "⪠": { "codepoints": [10912], "characters": "\u2AA0" }, + "⪝": { "codepoints": [10909], "characters": "\u2A9D" }, + "⪟": { "codepoints": [10911], "characters": "\u2A9F" }, + "≆": { "codepoints": [8774], "characters": "\u2246" }, + "⨤": { "codepoints": [10788], "characters": "\u2A24" }, + "⥲": { "codepoints": [10610], "characters": "\u2972" }, + "←": { "codepoints": [8592], "characters": "\u2190" }, + "∖": { "codepoints": [8726], "characters": "\u2216" }, + "⨳": { "codepoints": [10803], "characters": "\u2A33" }, + "⧤": { "codepoints": [10724], "characters": "\u29E4" }, + "∣": { "codepoints": [8739], "characters": "\u2223" }, + "⌣": { "codepoints": [8995], "characters": "\u2323" }, + "⪪": { "codepoints": [10922], "characters": "\u2AAA" }, + "⪬": { "codepoints": [10924], "characters": "\u2AAC" }, + "⪬︀": { "codepoints": [10924, 65024], "characters": "\u2AAC\uFE00" }, + "ь": { "codepoints": [1100], "characters": "\u044C" }, + "/": { "codepoints": [47], "characters": "\u002F" }, + "⧄": { "codepoints": [10692], "characters": "\u29C4" }, + "⌿": { "codepoints": [9023], "characters": "\u233F" }, + "𝕤": { "codepoints": [120164], "characters": "\uD835\uDD64" }, + "♠": { "codepoints": [9824], "characters": "\u2660" }, + "♠": { "codepoints": [9824], "characters": "\u2660" }, + "∥": { "codepoints": [8741], "characters": "\u2225" }, + "⊓": { "codepoints": [8851], "characters": "\u2293" }, + "⊓︀": { "codepoints": [8851, 65024], "characters": "\u2293\uFE00" }, + "⊔": { "codepoints": [8852], "characters": "\u2294" }, + "⊔︀": { "codepoints": [8852, 65024], "characters": "\u2294\uFE00" }, + "⊏": { "codepoints": [8847], "characters": "\u228F" }, + "⊑": { "codepoints": [8849], "characters": "\u2291" }, + "⊏": { "codepoints": [8847], "characters": "\u228F" }, + "⊑": { "codepoints": [8849], "characters": "\u2291" }, + "⊐": { "codepoints": [8848], "characters": "\u2290" }, + "⊒": { "codepoints": [8850], "characters": "\u2292" }, + "⊐": { "codepoints": [8848], "characters": "\u2290" }, + "⊒": { "codepoints": [8850], "characters": "\u2292" }, + "□": { "codepoints": [9633], "characters": "\u25A1" }, + "□": { "codepoints": [9633], "characters": "\u25A1" }, + "▪": { "codepoints": [9642], "characters": "\u25AA" }, + "▪": { "codepoints": [9642], "characters": "\u25AA" }, + "→": { "codepoints": [8594], "characters": "\u2192" }, + "𝓈": { "codepoints": [120008], "characters": "\uD835\uDCC8" }, + "∖": { "codepoints": [8726], "characters": "\u2216" }, + "⌣": { "codepoints": [8995], "characters": "\u2323" }, + "⋆": { "codepoints": [8902], "characters": "\u22C6" }, + "☆": { "codepoints": [9734], "characters": "\u2606" }, + "★": { "codepoints": [9733], "characters": "\u2605" }, + "ϵ": { "codepoints": [1013], "characters": "\u03F5" }, + "ϕ": { "codepoints": [981], "characters": "\u03D5" }, + "¯": { "codepoints": [175], "characters": "\u00AF" }, + "⊂": { "codepoints": [8834], "characters": "\u2282" }, + "⫅": { "codepoints": [10949], "characters": "\u2AC5" }, + "⪽": { "codepoints": [10941], "characters": "\u2ABD" }, + "⊆": { "codepoints": [8838], "characters": "\u2286" }, + "⫃": { "codepoints": [10947], "characters": "\u2AC3" }, + "⫁": { "codepoints": [10945], "characters": "\u2AC1" }, + "⫋": { "codepoints": [10955], "characters": "\u2ACB" }, + "⊊": { "codepoints": [8842], "characters": "\u228A" }, + "⪿": { "codepoints": [10943], "characters": "\u2ABF" }, + "⥹": { "codepoints": [10617], "characters": "\u2979" }, + "⊂": { "codepoints": [8834], "characters": "\u2282" }, + "⊆": { "codepoints": [8838], "characters": "\u2286" }, + "⫅": { "codepoints": [10949], "characters": "\u2AC5" }, + "⊊": { "codepoints": [8842], "characters": "\u228A" }, + "⫋": { "codepoints": [10955], "characters": "\u2ACB" }, + "⫇": { "codepoints": [10951], "characters": "\u2AC7" }, + "⫕": { "codepoints": [10965], "characters": "\u2AD5" }, + "⫓": { "codepoints": [10963], "characters": "\u2AD3" }, + "≻": { "codepoints": [8827], "characters": "\u227B" }, + "⪸": { "codepoints": [10936], "characters": "\u2AB8" }, + "≽": { "codepoints": [8829], "characters": "\u227D" }, + "⪰": { "codepoints": [10928], "characters": "\u2AB0" }, + "⪺": { "codepoints": [10938], "characters": "\u2ABA" }, + "⪶": { "codepoints": [10934], "characters": "\u2AB6" }, + "⋩": { "codepoints": [8937], "characters": "\u22E9" }, + "≿": { "codepoints": [8831], "characters": "\u227F" }, + "∑": { "codepoints": [8721], "characters": "\u2211" }, + "♪": { "codepoints": [9834], "characters": "\u266A" }, + "¹": { "codepoints": [185], "characters": "\u00B9" }, + "¹": { "codepoints": [185], "characters": "\u00B9" }, + "²": { "codepoints": [178], "characters": "\u00B2" }, + "²": { "codepoints": [178], "characters": "\u00B2" }, + "³": { "codepoints": [179], "characters": "\u00B3" }, + "³": { "codepoints": [179], "characters": "\u00B3" }, + "⊃": { "codepoints": [8835], "characters": "\u2283" }, + "⫆": { "codepoints": [10950], "characters": "\u2AC6" }, + "⪾": { "codepoints": [10942], "characters": "\u2ABE" }, + "⫘": { "codepoints": [10968], "characters": "\u2AD8" }, + "⊇": { "codepoints": [8839], "characters": "\u2287" }, + "⫄": { "codepoints": [10948], "characters": "\u2AC4" }, + "⟉": { "codepoints": [10185], "characters": "\u27C9" }, + "⫗": { "codepoints": [10967], "characters": "\u2AD7" }, + "⥻": { "codepoints": [10619], "characters": "\u297B" }, + "⫂": { "codepoints": [10946], "characters": "\u2AC2" }, + "⫌": { "codepoints": [10956], "characters": "\u2ACC" }, + "⊋": { "codepoints": [8843], "characters": "\u228B" }, + "⫀": { "codepoints": [10944], "characters": "\u2AC0" }, + "⊃": { "codepoints": [8835], "characters": "\u2283" }, + "⊇": { "codepoints": [8839], "characters": "\u2287" }, + "⫆": { "codepoints": [10950], "characters": "\u2AC6" }, + "⊋": { "codepoints": [8843], "characters": "\u228B" }, + "⫌": { "codepoints": [10956], "characters": "\u2ACC" }, + "⫈": { "codepoints": [10952], "characters": "\u2AC8" }, + "⫔": { "codepoints": [10964], "characters": "\u2AD4" }, + "⫖": { "codepoints": [10966], "characters": "\u2AD6" }, + "⇙": { "codepoints": [8665], "characters": "\u21D9" }, + "⤦": { "codepoints": [10534], "characters": "\u2926" }, + "↙": { "codepoints": [8601], "characters": "\u2199" }, + "↙": { "codepoints": [8601], "characters": "\u2199" }, + "⤪": { "codepoints": [10538], "characters": "\u292A" }, + "ß": { "codepoints": [223], "characters": "\u00DF" }, + "ß": { "codepoints": [223], "characters": "\u00DF" }, + "⌖": { "codepoints": [8982], "characters": "\u2316" }, + "τ": { "codepoints": [964], "characters": "\u03C4" }, + "⎴": { "codepoints": [9140], "characters": "\u23B4" }, + "ť": { "codepoints": [357], "characters": "\u0165" }, + "ţ": { "codepoints": [355], "characters": "\u0163" }, + "т": { "codepoints": [1090], "characters": "\u0442" }, + "⃛": { "codepoints": [8411], "characters": "\u20DB" }, + "⌕": { "codepoints": [8981], "characters": "\u2315" }, + "𝔱": { "codepoints": [120113], "characters": "\uD835\uDD31" }, + "∴": { "codepoints": [8756], "characters": "\u2234" }, + "∴": { "codepoints": [8756], "characters": "\u2234" }, + "θ": { "codepoints": [952], "characters": "\u03B8" }, + "ϑ": { "codepoints": [977], "characters": "\u03D1" }, + "ϑ": { "codepoints": [977], "characters": "\u03D1" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "∼": { "codepoints": [8764], "characters": "\u223C" }, + " ": { "codepoints": [8201], "characters": "\u2009" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "∼": { "codepoints": [8764], "characters": "\u223C" }, + "þ": { "codepoints": [254], "characters": "\u00FE" }, + "þ": { "codepoints": [254], "characters": "\u00FE" }, + "˜": { "codepoints": [732], "characters": "\u02DC" }, + "×": { "codepoints": [215], "characters": "\u00D7" }, + "×": { "codepoints": [215], "characters": "\u00D7" }, + "⊠": { "codepoints": [8864], "characters": "\u22A0" }, + "⨱": { "codepoints": [10801], "characters": "\u2A31" }, + "⨰": { "codepoints": [10800], "characters": "\u2A30" }, + "∭": { "codepoints": [8749], "characters": "\u222D" }, + "⤨": { "codepoints": [10536], "characters": "\u2928" }, + "⊤": { "codepoints": [8868], "characters": "\u22A4" }, + "⌶": { "codepoints": [9014], "characters": "\u2336" }, + "⫱": { "codepoints": [10993], "characters": "\u2AF1" }, + "𝕥": { "codepoints": [120165], "characters": "\uD835\uDD65" }, + "⫚": { "codepoints": [10970], "characters": "\u2ADA" }, + "⤩": { "codepoints": [10537], "characters": "\u2929" }, + "‴": { "codepoints": [8244], "characters": "\u2034" }, + "™": { "codepoints": [8482], "characters": "\u2122" }, + "▵": { "codepoints": [9653], "characters": "\u25B5" }, + "▿": { "codepoints": [9663], "characters": "\u25BF" }, + "◃": { "codepoints": [9667], "characters": "\u25C3" }, + "⊴": { "codepoints": [8884], "characters": "\u22B4" }, + "≜": { "codepoints": [8796], "characters": "\u225C" }, + "▹": { "codepoints": [9657], "characters": "\u25B9" }, + "⊵": { "codepoints": [8885], "characters": "\u22B5" }, + "◬": { "codepoints": [9708], "characters": "\u25EC" }, + "≜": { "codepoints": [8796], "characters": "\u225C" }, + "⨺": { "codepoints": [10810], "characters": "\u2A3A" }, + "⨹": { "codepoints": [10809], "characters": "\u2A39" }, + "⧍": { "codepoints": [10701], "characters": "\u29CD" }, + "⨻": { "codepoints": [10811], "characters": "\u2A3B" }, + "⏢": { "codepoints": [9186], "characters": "\u23E2" }, + "𝓉": { "codepoints": [120009], "characters": "\uD835\uDCC9" }, + "ц": { "codepoints": [1094], "characters": "\u0446" }, + "ћ": { "codepoints": [1115], "characters": "\u045B" }, + "ŧ": { "codepoints": [359], "characters": "\u0167" }, + "≬": { "codepoints": [8812], "characters": "\u226C" }, + "↞": { "codepoints": [8606], "characters": "\u219E" }, + "↠": { "codepoints": [8608], "characters": "\u21A0" }, + "⇑": { "codepoints": [8657], "characters": "\u21D1" }, + "⥣": { "codepoints": [10595], "characters": "\u2963" }, + "ú": { "codepoints": [250], "characters": "\u00FA" }, + "ú": { "codepoints": [250], "characters": "\u00FA" }, + "↑": { "codepoints": [8593], "characters": "\u2191" }, + "ў": { "codepoints": [1118], "characters": "\u045E" }, + "ŭ": { "codepoints": [365], "characters": "\u016D" }, + "û": { "codepoints": [251], "characters": "\u00FB" }, + "û": { "codepoints": [251], "characters": "\u00FB" }, + "у": { "codepoints": [1091], "characters": "\u0443" }, + "⇅": { "codepoints": [8645], "characters": "\u21C5" }, + "ű": { "codepoints": [369], "characters": "\u0171" }, + "⥮": { "codepoints": [10606], "characters": "\u296E" }, + "⥾": { "codepoints": [10622], "characters": "\u297E" }, + "𝔲": { "codepoints": [120114], "characters": "\uD835\uDD32" }, + "ù": { "codepoints": [249], "characters": "\u00F9" }, + "ù": { "codepoints": [249], "characters": "\u00F9" }, + "↿": { "codepoints": [8639], "characters": "\u21BF" }, + "↾": { "codepoints": [8638], "characters": "\u21BE" }, + "▀": { "codepoints": [9600], "characters": "\u2580" }, + "⌜": { "codepoints": [8988], "characters": "\u231C" }, + "⌜": { "codepoints": [8988], "characters": "\u231C" }, + "⌏": { "codepoints": [8975], "characters": "\u230F" }, + "◸": { "codepoints": [9720], "characters": "\u25F8" }, + "ū": { "codepoints": [363], "characters": "\u016B" }, + "¨": { "codepoints": [168], "characters": "\u00A8" }, + "¨": { "codepoints": [168], "characters": "\u00A8" }, + "ų": { "codepoints": [371], "characters": "\u0173" }, + "𝕦": { "codepoints": [120166], "characters": "\uD835\uDD66" }, + "↑": { "codepoints": [8593], "characters": "\u2191" }, + "↕": { "codepoints": [8597], "characters": "\u2195" }, + "↿": { "codepoints": [8639], "characters": "\u21BF" }, + "↾": { "codepoints": [8638], "characters": "\u21BE" }, + "⊎": { "codepoints": [8846], "characters": "\u228E" }, + "υ": { "codepoints": [965], "characters": "\u03C5" }, + "ϒ": { "codepoints": [978], "characters": "\u03D2" }, + "υ": { "codepoints": [965], "characters": "\u03C5" }, + "⇈": { "codepoints": [8648], "characters": "\u21C8" }, + "⌝": { "codepoints": [8989], "characters": "\u231D" }, + "⌝": { "codepoints": [8989], "characters": "\u231D" }, + "⌎": { "codepoints": [8974], "characters": "\u230E" }, + "ů": { "codepoints": [367], "characters": "\u016F" }, + "◹": { "codepoints": [9721], "characters": "\u25F9" }, + "𝓊": { "codepoints": [120010], "characters": "\uD835\uDCCA" }, + "⋰": { "codepoints": [8944], "characters": "\u22F0" }, + "ũ": { "codepoints": [361], "characters": "\u0169" }, + "▵": { "codepoints": [9653], "characters": "\u25B5" }, + "▴": { "codepoints": [9652], "characters": "\u25B4" }, + "⇈": { "codepoints": [8648], "characters": "\u21C8" }, + "ü": { "codepoints": [252], "characters": "\u00FC" }, + "ü": { "codepoints": [252], "characters": "\u00FC" }, + "⦧": { "codepoints": [10663], "characters": "\u29A7" }, + "⇕": { "codepoints": [8661], "characters": "\u21D5" }, + "⫨": { "codepoints": [10984], "characters": "\u2AE8" }, + "⫩": { "codepoints": [10985], "characters": "\u2AE9" }, + "⊨": { "codepoints": [8872], "characters": "\u22A8" }, + "⦜": { "codepoints": [10652], "characters": "\u299C" }, + "ϵ": { "codepoints": [1013], "characters": "\u03F5" }, + "ϰ": { "codepoints": [1008], "characters": "\u03F0" }, + "∅": { "codepoints": [8709], "characters": "\u2205" }, + "ϕ": { "codepoints": [981], "characters": "\u03D5" }, + "ϖ": { "codepoints": [982], "characters": "\u03D6" }, + "∝": { "codepoints": [8733], "characters": "\u221D" }, + "↕": { "codepoints": [8597], "characters": "\u2195" }, + "ϱ": { "codepoints": [1009], "characters": "\u03F1" }, + "ς": { "codepoints": [962], "characters": "\u03C2" }, + "⊊︀": { "codepoints": [8842, 65024], "characters": "\u228A\uFE00" }, + "⫋︀": { "codepoints": [10955, 65024], "characters": "\u2ACB\uFE00" }, + "⊋︀": { "codepoints": [8843, 65024], "characters": "\u228B\uFE00" }, + "⫌︀": { "codepoints": [10956, 65024], "characters": "\u2ACC\uFE00" }, + "ϑ": { "codepoints": [977], "characters": "\u03D1" }, + "⊲": { "codepoints": [8882], "characters": "\u22B2" }, + "⊳": { "codepoints": [8883], "characters": "\u22B3" }, + "в": { "codepoints": [1074], "characters": "\u0432" }, + "⊢": { "codepoints": [8866], "characters": "\u22A2" }, + "∨": { "codepoints": [8744], "characters": "\u2228" }, + "⊻": { "codepoints": [8891], "characters": "\u22BB" }, + "≚": { "codepoints": [8794], "characters": "\u225A" }, + "⋮": { "codepoints": [8942], "characters": "\u22EE" }, + "|": { "codepoints": [124], "characters": "\u007C" }, + "|": { "codepoints": [124], "characters": "\u007C" }, + "𝔳": { "codepoints": [120115], "characters": "\uD835\uDD33" }, + "⊲": { "codepoints": [8882], "characters": "\u22B2" }, + "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" }, + "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" }, + "𝕧": { "codepoints": [120167], "characters": "\uD835\uDD67" }, + "∝": { "codepoints": [8733], "characters": "\u221D" }, + "⊳": { "codepoints": [8883], "characters": "\u22B3" }, + "𝓋": { "codepoints": [120011], "characters": "\uD835\uDCCB" }, + "⫋︀": { "codepoints": [10955, 65024], "characters": "\u2ACB\uFE00" }, + "⊊︀": { "codepoints": [8842, 65024], "characters": "\u228A\uFE00" }, + "⫌︀": { "codepoints": [10956, 65024], "characters": "\u2ACC\uFE00" }, + "⊋︀": { "codepoints": [8843, 65024], "characters": "\u228B\uFE00" }, + "⦚": { "codepoints": [10650], "characters": "\u299A" }, + "ŵ": { "codepoints": [373], "characters": "\u0175" }, + "⩟": { "codepoints": [10847], "characters": "\u2A5F" }, + "∧": { "codepoints": [8743], "characters": "\u2227" }, + "≙": { "codepoints": [8793], "characters": "\u2259" }, + "℘": { "codepoints": [8472], "characters": "\u2118" }, + "𝔴": { "codepoints": [120116], "characters": "\uD835\uDD34" }, + "𝕨": { "codepoints": [120168], "characters": "\uD835\uDD68" }, + "℘": { "codepoints": [8472], "characters": "\u2118" }, + "≀": { "codepoints": [8768], "characters": "\u2240" }, + "≀": { "codepoints": [8768], "characters": "\u2240" }, + "𝓌": { "codepoints": [120012], "characters": "\uD835\uDCCC" }, + "⋂": { "codepoints": [8898], "characters": "\u22C2" }, + "◯": { "codepoints": [9711], "characters": "\u25EF" }, + "⋃": { "codepoints": [8899], "characters": "\u22C3" }, + "▽": { "codepoints": [9661], "characters": "\u25BD" }, + "𝔵": { "codepoints": [120117], "characters": "\uD835\uDD35" }, + "⟺": { "codepoints": [10234], "characters": "\u27FA" }, + "⟷": { "codepoints": [10231], "characters": "\u27F7" }, + "ξ": { "codepoints": [958], "characters": "\u03BE" }, + "⟸": { "codepoints": [10232], "characters": "\u27F8" }, + "⟵": { "codepoints": [10229], "characters": "\u27F5" }, + "⟼": { "codepoints": [10236], "characters": "\u27FC" }, + "⋻": { "codepoints": [8955], "characters": "\u22FB" }, + "⨀": { "codepoints": [10752], "characters": "\u2A00" }, + "𝕩": { "codepoints": [120169], "characters": "\uD835\uDD69" }, + "⨁": { "codepoints": [10753], "characters": "\u2A01" }, + "⨂": { "codepoints": [10754], "characters": "\u2A02" }, + "⟹": { "codepoints": [10233], "characters": "\u27F9" }, + "⟶": { "codepoints": [10230], "characters": "\u27F6" }, + "𝓍": { "codepoints": [120013], "characters": "\uD835\uDCCD" }, + "⨆": { "codepoints": [10758], "characters": "\u2A06" }, + "⨄": { "codepoints": [10756], "characters": "\u2A04" }, + "△": { "codepoints": [9651], "characters": "\u25B3" }, + "⋁": { "codepoints": [8897], "characters": "\u22C1" }, + "⋀": { "codepoints": [8896], "characters": "\u22C0" }, + "ý": { "codepoints": [253], "characters": "\u00FD" }, + "ý": { "codepoints": [253], "characters": "\u00FD" }, + "я": { "codepoints": [1103], "characters": "\u044F" }, + "ŷ": { "codepoints": [375], "characters": "\u0177" }, + "ы": { "codepoints": [1099], "characters": "\u044B" }, + "¥": { "codepoints": [165], "characters": "\u00A5" }, + "¥": { "codepoints": [165], "characters": "\u00A5" }, + "𝔶": { "codepoints": [120118], "characters": "\uD835\uDD36" }, + "ї": { "codepoints": [1111], "characters": "\u0457" }, + "𝕪": { "codepoints": [120170], "characters": "\uD835\uDD6A" }, + "𝓎": { "codepoints": [120014], "characters": "\uD835\uDCCE" }, + "ю": { "codepoints": [1102], "characters": "\u044E" }, + "ÿ": { "codepoints": [255], "characters": "\u00FF" }, + "ÿ": { "codepoints": [255], "characters": "\u00FF" }, + "ź": { "codepoints": [378], "characters": "\u017A" }, + "ž": { "codepoints": [382], "characters": "\u017E" }, + "з": { "codepoints": [1079], "characters": "\u0437" }, + "ż": { "codepoints": [380], "characters": "\u017C" }, + "ℨ": { "codepoints": [8488], "characters": "\u2128" }, + "ζ": { "codepoints": [950], "characters": "\u03B6" }, + "𝔷": { "codepoints": [120119], "characters": "\uD835\uDD37" }, + "ж": { "codepoints": [1078], "characters": "\u0436" }, + "⇝": { "codepoints": [8669], "characters": "\u21DD" }, + "𝕫": { "codepoints": [120171], "characters": "\uD835\uDD6B" }, + "𝓏": { "codepoints": [120015], "characters": "\uD835\uDCCF" }, + "‍": { "codepoints": [8205], "characters": "\u200D" }, + "‌": { "codepoints": [8204], "characters": "\u200C" } +} diff --git a/src/core/vendor/htmlEntities/entity.txt b/src/core/vendor/htmlEntities/entity.txt new file mode 100644 index 00000000..14696f83 --- /dev/null +++ b/src/core/vendor/htmlEntities/entity.txt @@ -0,0 +1,14 @@ +entity.json is the WHATWG named character reference set, retrieved verbatim from: + + https://html.spec.whatwg.org/entities.json + +It is used by src/core/config/scripts/generateHTMLEntities.mjs to generate the +shared HTML entity lookup tables (src/core/lib/HTMLEntities.mjs) consumed by the +"To HTML Entity" and "From HTML Entity" operations. + +Source: HTML Standard — 13.5 Named character references + https://html.spec.whatwg.org/multipage/named-characters.html + +Copyright and licence +--------------------- +Copyright © WHATWG (Apple, Google, Mozilla, Microsoft). This work is licensed under a Creative Commons Attribution 4.0 International License. To the extent portions of it are incorporated into source code, such portions in the source code are licensed under the BSD 3-Clause License instead. diff --git a/tests/node/tests/ToHTMLEntity.mjs b/tests/node/tests/ToHTMLEntity.mjs index 43d8cb49..2ac47f3d 100644 --- a/tests/node/tests/ToHTMLEntity.mjs +++ b/tests/node/tests/ToHTMLEntity.mjs @@ -1,7 +1,20 @@ import TestRegister from "../../lib/TestRegister.mjs"; import ToHTMLEntity from "../../../src/core/operations/ToHTMLEntity.mjs"; +import FromHTMLEntity from "../../../src/core/operations/FromHTMLEntity.mjs"; +import { HTML_ENTITY_LOOKUP, HTML_ENTITY_REVERSE_LOOKUP } from "../../../src/core/lib/HTMLEntities.mjs"; import it from "../assertionHandler.mjs"; import assert from "assert"; +import { readFileSync } from "fs"; +import { fileURLToPath } from "url"; +import path from "path"; + +// Vendored WHATWG named character reference set (https://html.spec.whatwg.org/entities.json). +const SPEC = JSON.parse(readFileSync(path.join( + path.dirname(fileURLToPath(import.meta.url)), + "../../../src/core/vendor/htmlEntities/entity.json"), "utf8")); +const specByName = {}; +for (const [key, val] of Object.entries(SPEC)) + if (key.endsWith(";")) specByName[key.slice(1, -1)] = val.codepoints; TestRegister.addApiTests([ it("To HTML Entity: every named entity in the table is well-formed", () => { @@ -30,4 +43,40 @@ TestRegister.addApiTests([ } assert.deepStrictEqual(malformed, [], `Malformed entity value(s) near: ${JSON.stringify(malformed)}`); }), + + it("HTML Entity: named encoding round-trips through From HTML Entity", () => { + // Because both operations share one lookup table, encoding a character to + // a named entity and decoding it must return the original character for + // every BMP code point: FromHTMLEntity(ToHTMLEntity(x)) === x. + const toOp = new ToHTMLEntity(), + fromOp = new FromHTMLEntity(); + const mismatches = []; + for (let cp = 0; cp <= 0xFFFF; cp++) { + if (cp >= 0xD800 && cp <= 0xDFFF) continue; // skip surrogate range + const char = String.fromCodePoint(cp); + const encoded = toOp.run(char, [true, "Named entities"]); + const decoded = fromOp.run(encoded, []); + if (decoded !== char) + mismatches.push(`U+${cp.toString(16).toUpperCase().padStart(4, "0")} -> ${encoded} -> U+${decoded.codePointAt(0).toString(16).toUpperCase()}`); + } + assert.deepStrictEqual(mismatches, [], `Round-trip failed for: ${JSON.stringify(mismatches.slice(0, 20))}`); + }), + + it("HTML Entity: every table entry is conformant with the WHATWG spec", () => { + // Both lookup tables must agree with entities.json: every encode name is a + // real spec name mapping to exactly that code point, and every decode name + // maps to the spec code point. + const violations = []; + for (const [cp, name] of Object.entries(HTML_ENTITY_LOOKUP)) { + const spec = specByName[name]; + if (!spec || spec.length !== 1 || spec[0] !== Number(cp)) + violations.push(`encode ${cp} -> &${name}; (spec: ${spec ? JSON.stringify(spec) : "none"})`); + } + for (const [name, cp] of Object.entries(HTML_ENTITY_REVERSE_LOOKUP)) { + const spec = specByName[name]; + if (!spec || spec.length !== 1 || spec[0] !== cp) + violations.push(`decode &${name}; -> ${cp} (spec: ${spec ? JSON.stringify(spec) : "none"})`); + } + assert.deepStrictEqual(violations, [], `Spec violations: ${JSON.stringify(violations.slice(0, 20))}`); + }), ]); diff --git a/tests/operations/tests/HTMLEntity.mjs b/tests/operations/tests/HTMLEntity.mjs new file mode 100644 index 00000000..e77433a8 --- /dev/null +++ b/tests/operations/tests/HTMLEntity.mjs @@ -0,0 +1,126 @@ +/** + * To/From HTML Entity tests. + * + * @author roberson-io [michaelroberson@gmail.com] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "To HTML Entity: named", + input: "\"&\"", + expectedOutput: "<a href='#'>"&"</a>", + recipeConfig: [ + { op: "To HTML Entity", args: [false, "Named entities"] }, + ], + }, + { + // Regression: this value was "⇌;" (stray trailing semicolon) in the + // original byteToEntity table. See issue #2645. + name: "To HTML Entity: malformed value fixed (U+21CC)", + input: "⇌", + expectedOutput: "⇌", + recipeConfig: [ + { op: "To HTML Entity", args: [false, "Named entities"] }, + ], + }, + { + // Regression: U+03F5 emitted "ε," before; the spec name is ϵ + // (ε is U+03B5). See issue #2645. + name: "To HTML Entity: spec-conformant name (U+03F5)", + input: "ϵ", + expectedOutput: "ϵ", + recipeConfig: [ + { op: "To HTML Entity", args: [false, "Named entities"] }, + ], + }, + { + // Regression: the OHM SIGN previously emitted the non-conformant Ω + // (spec Ω is U+03A9). It now encodes numerically. + name: "To HTML Entity: non-conformant name dropped (U+2126 ohm sign)", + input: "Ω", + expectedOutput: "Ω", + recipeConfig: [ + { op: "To HTML Entity", args: [false, "Named entities"] }, + ], + }, + { + // The FULL BLOCK previously decoded to █ but never encoded to it. + // Generating both tables from the spec fixes this asymmetry. + name: "To HTML Entity: encode/decode symmetry (U+2588 block)", + input: "█", + expectedOutput: "█", + recipeConfig: [ + { op: "To HTML Entity", args: [false, "Named entities"] }, + ], + }, + { + name: "To HTML Entity: numeric, convert all", + input: "A<", + expectedOutput: "A<", + recipeConfig: [ + { op: "To HTML Entity", args: [true, "Numeric entities"] }, + ], + }, + { + name: "To HTML Entity: hex, convert all", + input: "A<", + expectedOutput: "A<", + recipeConfig: [ + { op: "To HTML Entity", args: [true, "Hex entities"] }, + ], + }, + { + name: "From HTML Entity: named", + input: "<a href='#'>"&"</a>", + expectedOutput: "\"&\"", + recipeConfig: [ + { op: "From HTML Entity", args: [] }, + ], + }, + { + // Legacy/alias names still decode, now to the spec code point. + name: "From HTML Entity: alias to spec code point (Ω)", + input: "Ω", + expectedOutput: "Ω", + recipeConfig: [ + { op: "From HTML Entity", args: [] }, + ], + }, + { + name: "From HTML Entity: decimal numeric entity", + input: "A", + expectedOutput: "A", + recipeConfig: [ + { op: "From HTML Entity", args: [] }, + ], + }, + { + name: "From HTML Entity: hex numeric entity", + input: "A", + expectedOutput: "A", + recipeConfig: [ + { op: "From HTML Entity", args: [] }, + ], + }, + { + // Unknown entities are passed through unchanged. + name: "From HTML Entity: invalid entity passed through", + input: "a ¬real; b", + expectedOutput: "a ¬real; b", + recipeConfig: [ + { op: "From HTML Entity", args: [] }, + ], + }, + { + name: "HTML Entity: round-trips through both operations", + input: "Hello & \"friends\" — ⇌ █", + expectedOutput: "Hello & \"friends\" — ⇌ █", + recipeConfig: [ + { op: "To HTML Entity", args: [true, "Named entities"] }, + { op: "From HTML Entity", args: [] }, + ], + }, +]); From d1722827558fe02afd6ee6b83cb7f70c4eddb98b Mon Sep 17 00:00:00 2001 From: p-leriche <7701190+p-leriche@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:04:13 +0100 Subject: [PATCH 11/25] Add Modular Inverse operation (#2207) --- src/core/config/Categories.json | 1 + src/core/operations/ModularInverse.mjs | 107 ++++++++++++++++++++++ tests/operations/tests/ModularInverse.mjs | 78 ++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 src/core/operations/ModularInverse.mjs create mode 100644 tests/operations/tests/ModularInverse.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 20846692..a9d54d7b 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -244,6 +244,7 @@ "Multiply", "Divide", "Extended GCD", + "Modular Inverse", "Mean", "Median", "Standard Deviation", diff --git a/src/core/operations/ModularInverse.mjs b/src/core/operations/ModularInverse.mjs new file mode 100644 index 00000000..88f4bff7 --- /dev/null +++ b/src/core/operations/ModularInverse.mjs @@ -0,0 +1,107 @@ +/** + * @author p-leriche [philip.leriche@cantab.net] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { parseBigInt, egcd } from "../lib/BigIntUtils.mjs"; + + +/* ---------- operation class ---------- */ + +/** + * Modular Inverse operation + */ +class ModularInverse extends Operation { + + /** + * ModularInverse constructor + */ + constructor() { + super(); + + this.name = "Modular Inverse"; + this.module = "Crypto"; + this.description = + "Computes the modular multiplicative inverse of a modulo m.

" + + "Finds x such that a*x = 1 (mod m).

" + + "Input handling: If either a or m is left blank, " + + "its value is taken from the Input field."; + this.infoURL = "https://wikipedia.org/wiki/Modular_multiplicative_inverse"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Value (a)", + type: "string", + value: "" + }, + { + name: "Modulus (m)", + type: "string", + value: "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [aStr, mStr] = args; + + // Trim everything so "" and " " count as empty + const aParam = aStr?.trim(); + const mParam = mStr?.trim(); + const inputVal = input?.trim(); + + let a, m; + + if (aParam && mParam) { + // Case 1: value and modulus both given as parameters + a = aParam; + m = mParam; + } else if (!aParam && mParam) { + // Case 2: value missing - take from input + a = inputVal; + m = mParam; + if (!a) throw new OperationError("Value (a) must be defined"); + } else if (aParam && !mParam) { + // Case 3: modulus missing - take from input + a = aParam; + m = inputVal; + if (!m) throw new OperationError("Modulus (m) must be defined"); + } else if (!aParam && !mParam) { + // Case 4: value and modulus both missing + throw new OperationError("Value (a) and Modulus (m) must be defined"); + } + + const aBI = parseBigInt(a, "Value (a)"); + const mBI = parseBigInt(m, "Modulus (m)"); + + if (mBI <= 0n) { + throw new OperationError("Modulus must be greater than zero"); + } + + const aNorm = ((aBI % mBI) + mBI) % mBI; + const [g, x] = egcd(aNorm, mBI); + + if (g !== 1n && g !== -1n) { + throw new OperationError("Inverse does not exist because gcd(a, m) ≠ 1"); + } + + let inv = x; + if (g === -1n) inv = -inv; + + inv = ((inv % mBI) + mBI) % mBI; + + + return inv.toString(); + } +} + +export default ModularInverse; diff --git a/tests/operations/tests/ModularInverse.mjs b/tests/operations/tests/ModularInverse.mjs new file mode 100644 index 00000000..53cb46a0 --- /dev/null +++ b/tests/operations/tests/ModularInverse.mjs @@ -0,0 +1,78 @@ +/** + * Modular Inverse tests. + * + * @author p-leriche [philip.leriche@cantab.net] + * + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Modular Inverse: basic example (3 mod 11)", + input: "", + expectedOutput: "4", + recipeConfig: [ + { + op: "Modular Inverse", + args: ["3", "11"], + }, + ], + }, + { + name: "Modular Inverse: another coprime pair (7 mod 26)", + input: "", + expectedOutput: "15", + recipeConfig: [ + { + op: "Modular Inverse", + args: ["7", "26"], + }, + ], + }, + { + name: "Modular Inverse: hexadecimal input (0x10 mod 0x11)", + input: "", + expectedOutput: "16", + recipeConfig: [ + { + op: "Modular Inverse", + args: ["0x10", "0x11"], + }, + ], + }, + { + name: "Modular Inverse: using input field for value", + input: "5", + expectedOutput: "21", + recipeConfig: [ + { + op: "Modular Inverse", + args: ["", "26"], + }, + ], + }, + { + name: "Modular Inverse: using input field for modulus", + input: "17", + expectedOutput: "7", + recipeConfig: [ + { + op: "Modular Inverse", + args: ["5", ""], + }, + ], + }, + { + name: "Modular Inverse: large number (RSA-like)", + input: "", + expectedOutput: "934281398294", + recipeConfig: [ + { + op: "Modular Inverse", + args: ["65537", "9999999999999"], + }, + ], + }, +]); From c7898130e7e6a1afbfd00d3fce7b7fc6d9b1e2a7 Mon Sep 17 00:00:00 2001 From: p-leriche <7701190+p-leriche@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:09:06 +0100 Subject: [PATCH 12/25] Add Generate Prime Number operation (#2212) --- src/core/config/Categories.json | 1 + src/core/operations/GeneratePrime.mjs | 154 ++++++++++++++++++++++++++ src/core/operations/RandomPrime.mjs | 154 ++++++++++++++++++++++++++ 3 files changed, 309 insertions(+) create mode 100644 src/core/operations/GeneratePrime.mjs create mode 100644 src/core/operations/RandomPrime.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index a9d54d7b..bf2c3118 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -175,6 +175,7 @@ "AES Key Wrap", "AES Key Unwrap", "Pseudo-Random Number Generator", + "Pseudo-Random Prime Generator", "Enigma", "Bombe", "Multiple Bombe", diff --git a/src/core/operations/GeneratePrime.mjs b/src/core/operations/GeneratePrime.mjs new file mode 100644 index 00000000..3509045f --- /dev/null +++ b/src/core/operations/GeneratePrime.mjs @@ -0,0 +1,154 @@ +/** + * @author p-leriche [philip.leriche@cantab.net] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { modPow } from "../lib/BigIntUtils.mjs"; + +/* ---------- helper functions ---------- */ + +/** + * Generate random BigInt with specified bit length + */ +function randBigInt(bits) { + const bytes = Math.ceil(bits / 8); + const a = new Uint8Array(bytes); + crypto.getRandomValues(a); + + // Set high bit to ensure correct bit length + a[0] |= 1 << (7 - ((8 * bytes - bits))); + // Set low bit to ensure odd (primes > 2 are odd) + a[bytes - 1] |= 1; + + let h = ""; + for (const b of a) h += b.toString(16).padStart(2, "0"); + return BigInt("0x" + h); +} + +/** + * Miller-Rabin primality test + */ +function isProbablePrime(n, rounds) { + if (n < 2n) return false; + if (n === 2n || n === 3n) return true; + if (n % 2n === 0n) return false; + + // Write n-1 as 2^r * d + let d = n - 1n; + let r = 0n; + while (d % 2n === 0n) { + d /= 2n; + r++; + } + + // Witness loop + for (let i = 0; i < rounds; i++) { + const a = randBigInt(n.toString(2).length - 1) % (n - 3n) + 2n; + let x = modPow(a, d, n); + + if (x === 1n || x === n - 1n) continue; + + let composite = true; + for (let j = 0n; j < r - 1n; j++) { + x = modPow(x, 2n, n); + if (x === n - 1n) { + composite = false; + break; + } + } + + if (composite) return false; + } + + return true; +} + +/* ---------- operation class ---------- */ + +/** + * Generate Prime Number operation + */ +class GeneratePrime extends Operation { + /** + * GeneratePrime constructor + */ + constructor() { + super(); + + this.name = "Pseudo-Random Prime Generator"; + this.module = "Crypto"; + this.description = + "Generates a random probable prime number of specified bit length using the Miller-Rabin primality test.

" + + "Primality guarantee:
" + + "For numbers . 3,317, the result is guaranteed prime (deterministic test).
" + + "For larger numbers, uses probabilistic testing:
" + + "- Standard (7 rounds): Probability of composite approx 1 in 16,000)

" + + "- Crypto grade (40 rounds): Probability of composite(approx 1 in 10^24)
" + + "Crypto grade is recommended for cryptographic applications (RSA, Diffie-Hellman, etc.).

" ; + this.infoURL = "https://wikipedia.org/wiki/Miller-Rabin_primality_test"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Bit length", + type: "number", + value: 512, + min: 2 + }, + { + name: "Crypto grade", + type: "boolean", + value: false + }, + { + name: "Output format", + type: "option", + value: ["Decimal", "Hexadecimal"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [bits, cryptoGrade, outputFormat] = args; + + if (bits < 2) { + throw new OperationError("Bit length must be at least 2"); + } + + if (bits > 4096) { + throw new OperationError("Bit length limited to 4096 bits for performance reasons"); + } + + const rounds = cryptoGrade ? 40 : 7; + let attempts = 0; + const maxAttempts = 10000; + + let n = randBigInt(bits); + + while (!isProbablePrime(n, rounds)) { + n = randBigInt(bits); + attempts++; + + if (attempts > maxAttempts) { + throw new OperationError(`Failed to generate prime after ${maxAttempts} attempts. Try a different bit length.`); + } + } + + // Return only the prime for pipeability + if (outputFormat === "Hexadecimal") { + return "0x" + n.toString(16); + } else { + return n.toString(); + } + } +} + +export default GeneratePrime; diff --git a/src/core/operations/RandomPrime.mjs b/src/core/operations/RandomPrime.mjs new file mode 100644 index 00000000..3509045f --- /dev/null +++ b/src/core/operations/RandomPrime.mjs @@ -0,0 +1,154 @@ +/** + * @author p-leriche [philip.leriche@cantab.net] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { modPow } from "../lib/BigIntUtils.mjs"; + +/* ---------- helper functions ---------- */ + +/** + * Generate random BigInt with specified bit length + */ +function randBigInt(bits) { + const bytes = Math.ceil(bits / 8); + const a = new Uint8Array(bytes); + crypto.getRandomValues(a); + + // Set high bit to ensure correct bit length + a[0] |= 1 << (7 - ((8 * bytes - bits))); + // Set low bit to ensure odd (primes > 2 are odd) + a[bytes - 1] |= 1; + + let h = ""; + for (const b of a) h += b.toString(16).padStart(2, "0"); + return BigInt("0x" + h); +} + +/** + * Miller-Rabin primality test + */ +function isProbablePrime(n, rounds) { + if (n < 2n) return false; + if (n === 2n || n === 3n) return true; + if (n % 2n === 0n) return false; + + // Write n-1 as 2^r * d + let d = n - 1n; + let r = 0n; + while (d % 2n === 0n) { + d /= 2n; + r++; + } + + // Witness loop + for (let i = 0; i < rounds; i++) { + const a = randBigInt(n.toString(2).length - 1) % (n - 3n) + 2n; + let x = modPow(a, d, n); + + if (x === 1n || x === n - 1n) continue; + + let composite = true; + for (let j = 0n; j < r - 1n; j++) { + x = modPow(x, 2n, n); + if (x === n - 1n) { + composite = false; + break; + } + } + + if (composite) return false; + } + + return true; +} + +/* ---------- operation class ---------- */ + +/** + * Generate Prime Number operation + */ +class GeneratePrime extends Operation { + /** + * GeneratePrime constructor + */ + constructor() { + super(); + + this.name = "Pseudo-Random Prime Generator"; + this.module = "Crypto"; + this.description = + "Generates a random probable prime number of specified bit length using the Miller-Rabin primality test.

" + + "Primality guarantee:
" + + "For numbers . 3,317, the result is guaranteed prime (deterministic test).
" + + "For larger numbers, uses probabilistic testing:
" + + "- Standard (7 rounds): Probability of composite approx 1 in 16,000)

" + + "- Crypto grade (40 rounds): Probability of composite(approx 1 in 10^24)
" + + "Crypto grade is recommended for cryptographic applications (RSA, Diffie-Hellman, etc.).

" ; + this.infoURL = "https://wikipedia.org/wiki/Miller-Rabin_primality_test"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Bit length", + type: "number", + value: 512, + min: 2 + }, + { + name: "Crypto grade", + type: "boolean", + value: false + }, + { + name: "Output format", + type: "option", + value: ["Decimal", "Hexadecimal"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [bits, cryptoGrade, outputFormat] = args; + + if (bits < 2) { + throw new OperationError("Bit length must be at least 2"); + } + + if (bits > 4096) { + throw new OperationError("Bit length limited to 4096 bits for performance reasons"); + } + + const rounds = cryptoGrade ? 40 : 7; + let attempts = 0; + const maxAttempts = 10000; + + let n = randBigInt(bits); + + while (!isProbablePrime(n, rounds)) { + n = randBigInt(bits); + attempts++; + + if (attempts > maxAttempts) { + throw new OperationError(`Failed to generate prime after ${maxAttempts} attempts. Try a different bit length.`); + } + } + + // Return only the prime for pipeability + if (outputFormat === "Hexadecimal") { + return "0x" + n.toString(16); + } else { + return n.toString(); + } + } +} + +export default GeneratePrime; From 5a01c710986db2dd774a836dc1194123366c8191 Mon Sep 17 00:00:00 2001 From: min23asdw <76154445+min23asdw@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:10:40 +0700 Subject: [PATCH 13/25] fix: fromDecimal Auto delimiter now correctly parses multiple numbers (#2270) --- src/core/lib/Decimal.mjs | 10 ++--- src/core/operations/FromDecimal.mjs | 7 +++- tests/operations/tests/FromDecimal.mjs | 55 ++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/src/core/lib/Decimal.mjs b/src/core/lib/Decimal.mjs index a140fd4e..e0b36369 100644 --- a/src/core/lib/Decimal.mjs +++ b/src/core/lib/Decimal.mjs @@ -24,12 +24,12 @@ import Utils from "../Utils.mjs"; * fromDecimal("10:20:30", "Colon"); */ export function fromDecimal(data, delim="Auto") { - delim = Utils.charRep(delim); - const output = []; - let byteStr = data.split(delim); - if (byteStr[byteStr.length-1] === "") - byteStr = byteStr.slice(0, byteStr.length-1); + const delimRegex = delim === "Auto" ? /[^\d-]+/ : Utils.regexRep(delim); + let byteStr = data.split(delimRegex); + byteStr = byteStr.filter(str => str !== ""); + + const output = []; for (let i = 0; i < byteStr.length; i++) { output[i] = parseInt(byteStr[i], 10); } diff --git a/src/core/operations/FromDecimal.mjs b/src/core/operations/FromDecimal.mjs index f98931d6..16af47e9 100644 --- a/src/core/operations/FromDecimal.mjs +++ b/src/core/operations/FromDecimal.mjs @@ -8,6 +8,11 @@ import Operation from "../Operation.mjs"; import {DELIM_OPTIONS} from "../lib/Delim.mjs"; import {fromDecimal} from "../lib/Decimal.mjs"; +/** + * From Decimal delimiters, plus auto-detection. + */ +const FROM_DECIMAL_DELIM_OPTIONS = [...DELIM_OPTIONS, "Auto"]; + /** * From Decimal operation */ @@ -28,7 +33,7 @@ class FromDecimal extends Operation { { "name": "Delimiter", "type": "option", - "value": DELIM_OPTIONS + "value": FROM_DECIMAL_DELIM_OPTIONS }, { "name": "Support signed values", diff --git a/tests/operations/tests/FromDecimal.mjs b/tests/operations/tests/FromDecimal.mjs index dfc440ec..a8711840 100644 --- a/tests/operations/tests/FromDecimal.mjs +++ b/tests/operations/tests/FromDecimal.mjs @@ -30,4 +30,59 @@ TestRegister.addTests([ }, ], }, + { + name: "From Decimal with Auto delimiter (space)", + input: "72 101 108 108 111", + expectedOutput: "Hello", + recipeConfig: [ + { + op: "From Decimal", + args: ["Auto", false] + }, + ], + }, + { + name: "From Decimal with Auto delimiter (comma)", + input: "72,101,108,108,111", + expectedOutput: "Hello", + recipeConfig: [ + { + op: "From Decimal", + args: ["Auto", false] + }, + ], + }, + { + name: "From Decimal with Auto delimiter (mixed)", + input: "72, 101 : 108; 108\t111", + expectedOutput: "Hello", + recipeConfig: [ + { + op: "From Decimal", + args: ["Auto", false] + }, + ], + }, + { + name: "From Decimal with Auto delimiter (newline)", + input: "72\n101\n108\n108\n111", + expectedOutput: "Hello", + recipeConfig: [ + { + op: "From Decimal", + args: ["Auto", false] + }, + ], + }, + { + name: "From Decimal with Auto delimiter and signed values", + input: "-130 -140 -152 -151 115 33 0 -1", + expectedOutput: "~this!\u0000\u00ff", + recipeConfig: [ + { + op: "From Decimal", + args: ["Auto", true] + }, + ], + }, ]); From 4a53231a71c0ae3e618d4f22bf77ed92da60d0d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:11:35 +0000 Subject: [PATCH 14/25] chore (deps): bump morgan from 1.10.1 to 1.11.0 (#2676) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index cde26b14..a8068e3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13610,20 +13610,24 @@ } }, "node_modules/morgan": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", - "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.11.0.tgz", + "integrity": "sha512-zSkVu3t18r39pw4ixfBKvfZi3y2UOqr7d4WYwcj3m8nXpEQK4rPO6GLzs/CExoRgmX3y9EjmmcXqv6jq0SK46g==", "dev": true, "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", "depd": "~2.0.0", - "on-finished": "~2.3.0", + "on-finished": "~2.4.1", "on-headers": "~1.1.0" }, "engines": { "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/morgan/node_modules/debug": { @@ -13643,6 +13647,19 @@ "dev": true, "license": "MIT" }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", From df545e62b9228d5b1a2099677181fd3b16d866d5 Mon Sep 17 00:00:00 2001 From: Michael Roberson Date: Tue, 21 Jul 2026 11:12:30 -0400 Subject: [PATCH 15/25] fix: await Node API operations whose run() returns a non-async Promise (#2659) --- src/core/operations/AvroToJSON.mjs | 2 +- src/core/operations/Bzip2Compress.mjs | 2 +- tests/node/tests/operations.mjs | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/core/operations/AvroToJSON.mjs b/src/core/operations/AvroToJSON.mjs index 497a3872..585bd872 100644 --- a/src/core/operations/AvroToJSON.mjs +++ b/src/core/operations/AvroToJSON.mjs @@ -39,7 +39,7 @@ class AvroToJSON extends Operation { * @param {Object[]} args * @returns {string} */ - run(input, args) { + async run(input, args) { if (input.byteLength <= 0) { throw new OperationError("Please provide an input."); } diff --git a/src/core/operations/Bzip2Compress.mjs b/src/core/operations/Bzip2Compress.mjs index 45dbfae6..b5105e98 100644 --- a/src/core/operations/Bzip2Compress.mjs +++ b/src/core/operations/Bzip2Compress.mjs @@ -47,7 +47,7 @@ class Bzip2Compress extends Operation { * @param {Object[]} args * @returns {File} */ - run(input, args) { + async run(input, args) { const [blockSize, workFactor] = args; if (input.byteLength <= 0) { throw new OperationError("Please provide an input."); diff --git a/tests/node/tests/operations.mjs b/tests/node/tests/operations.mjs index 0046574c..c6f0e44f 100644 --- a/tests/node/tests/operations.mjs +++ b/tests/node/tests/operations.mjs @@ -264,6 +264,18 @@ Full hash: $2a$10$ODeP1.6fMsb.ENk2ngPUCO7qTGVPyHA9TqDVcyupyed8FjsiF65L6`; assert.strictEqual(result.toString(), "Fit as a Fiddle"); }), + it("Bzip2 Compress: round-trips through the Node API", async () => { + const compressed = await chef.bzip2Compress("The quick brown fox."); + const result = await chef.bzip2Decompress(compressed); + assert.strictEqual(result.toString(), "The quick brown fox."); + }), + + it("Avro to JSON: decodes an object container file through the Node API", async () => { + const avro = chef.fromHex("4f626a0104166176726f2e736368656d6196017b2274797065223a227265636f7264222c226e616d65223a22736d616c6c222c226669656c6473223a5b7b226e616d65223a226e616d65222c2274797065223a22737472696e67227d5d7d146176726f2e636f646563086e756c6c004e0247632e3702e5b75cdab9a62f1541020e0c6d796e616d654e0247632e3702e5b75cdab9a62f1541"); + const result = await chef.avroToJSON(avro); + assert.strictEqual(result.toString(), "{\n \"name\": \"myname\"\n}"); + }), + it("cartesianProduct: binary string", () => { const result = cartesianProduct("1:2\\n\\n3:4", { itemDelimiter: ":", From 88a2dbab9d6c0f66a5eacb89cd1bcb2115977d4d Mon Sep 17 00:00:00 2001 From: alleria173 Date: Tue, 21 Jul 2026 18:25:12 +0100 Subject: [PATCH 16/25] Add HMAC regression tests for Decimal key parsing (#2680) --- tests/operations/tests/Hash.mjs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/operations/tests/Hash.mjs b/tests/operations/tests/Hash.mjs index 1ffb749c..a805b94f 100644 --- a/tests/operations/tests/Hash.mjs +++ b/tests/operations/tests/Hash.mjs @@ -722,6 +722,39 @@ TestRegister.addTests([ } ] }, + { + name: "HMAC: Decimal key space-delimited matches Latin1 key (fromDecimal Auto delimiter regression)", + input: "Hello, World!", + expectedOutput: "9b641a008211bb0464a10899d5b37a24ab08ffcf839f5e74d17d99f856530957", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "Decimal", "string": "65 65"}, "SHA256"] + } + ] + }, + { + name: "HMAC: Latin1 key matches space-delimited Decimal key (fromDecimal Auto delimiter regression)", + input: "Hello, World!", + expectedOutput: "9b641a008211bb0464a10899d5b37a24ab08ffcf839f5e74d17d99f856530957", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "Latin1", "string": "AA"}, "SHA256"] + } + ] + }, + { + name: "HMAC: Decimal key comma-delimited matches Latin1 key (fromDecimal Auto delimiter regression)", + input: "Hello, World!", + expectedOutput: "9b641a008211bb0464a10899d5b37a24ab08ffcf839f5e74d17d99f856530957", + recipeConfig: [ + { + "op": "HMAC", + "args": [{"option": "Decimal", "string": "65,65"}, "SHA256"] + } + ] + }, { name: "MD5: Complex bytes", input: "10dc10e32010de10d010dc10d810d910d010e12e", From 1343a107cbfc7e9633493b53b62c8a5cf822b5f2 Mon Sep 17 00:00:00 2001 From: Thomas Nemer <80506610+thomasnemer@users.noreply.github.com> Date: Thu, 23 Jul 2026 09:27:49 +0200 Subject: [PATCH 17/25] feat: add modulo operation (#2103) Co-authored-by: GCHQDeveloper581 <63102987+GCHQDeveloper581@users.noreply.github.com> (Minor tweaks to pass CI) --- src/core/config/Categories.json | 1 + src/core/lib/Arithmetic.mjs | 13 ++ src/core/operations/FromDecimal.mjs | 2 +- src/core/operations/MOD.mjs | 62 +++++++++ tests/operations/tests/MOD.mjs | 208 ++++++++++++++++++++++++++++ 5 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/MOD.mjs create mode 100644 tests/operations/tests/MOD.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index bf2c3118..4f4dedc2 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -244,6 +244,7 @@ "Subtract", "Multiply", "Divide", + "MOD", "Extended GCD", "Modular Inverse", "Mean", diff --git a/src/core/lib/Arithmetic.mjs b/src/core/lib/Arithmetic.mjs index 5fbc5771..d951253c 100644 --- a/src/core/lib/Arithmetic.mjs +++ b/src/core/lib/Arithmetic.mjs @@ -124,6 +124,19 @@ export function median(data) { } +/** + * Computes modulo of two numbers and returns the value. + * + * @param {BigNumber[]} data + * @returns {BigNumber} + */ +export function mod(data) { + if (data.length > 0) { + return data.reduce((acc, curr) => acc.mod(curr)); + } +} + + /** * Computes standard deviation of a number array and returns the value. * diff --git a/src/core/operations/FromDecimal.mjs b/src/core/operations/FromDecimal.mjs index 16af47e9..f718fb50 100644 --- a/src/core/operations/FromDecimal.mjs +++ b/src/core/operations/FromDecimal.mjs @@ -9,7 +9,7 @@ import {DELIM_OPTIONS} from "../lib/Delim.mjs"; import {fromDecimal} from "../lib/Decimal.mjs"; /** - * From Decimal delimiters, plus auto-detection. + * From Decimal delimiters, plus auto-detection. */ const FROM_DECIMAL_DELIM_OPTIONS = [...DELIM_OPTIONS, "Auto"]; diff --git a/src/core/operations/MOD.mjs b/src/core/operations/MOD.mjs new file mode 100644 index 00000000..e50b339a --- /dev/null +++ b/src/core/operations/MOD.mjs @@ -0,0 +1,62 @@ +/** + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation.mjs"; +import { createNumArray } from "../lib/Arithmetic.mjs"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs"; + + +/** + * MOD operation + */ +class MOD extends Operation { + + /** + * MOD constructor + */ + constructor() { + super(); + + this.name = "MOD"; + this.module = "Default"; + this.description = "Computes the modulo of each number in a list with a given modulus value. Numbers are extracted from the input based on the delimiter, and non-numeric values are ignored.

e.g. 15 4 7 with modulus 3 becomes 0 1 1"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Modulus", + "type": "number", + "value": 2 + }, + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const modulus = new BigNumber(args[0]); + const delimiter = args[1]; + + if (modulus.isZero()) { + throw new Error("Modulus cannot be zero"); + } + + const numbers = createNumArray(input, delimiter); + const results = numbers.map(num => num.mod(modulus)); + + return results.join(" "); + } + +} + +export default MOD; diff --git a/tests/operations/tests/MOD.mjs b/tests/operations/tests/MOD.mjs new file mode 100644 index 00000000..2ecd25f6 --- /dev/null +++ b/tests/operations/tests/MOD.mjs @@ -0,0 +1,208 @@ +/** + * MOD tests + * + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "MOD: Basic modulo operation", + input: "15 4 7", + expectedOutput: "0 1 1", + recipeConfig: [ + { + "op": "MOD", + "args": [3, "Space"] + } + ], + }, + { + name: "MOD: Single number", + input: "10", + expectedOutput: "1", + recipeConfig: [ + { + "op": "MOD", + "args": [3, "Space"] + } + ], + }, + { + name: "MOD: Comma-separated numbers", + input: "15,8,23,16,5", + expectedOutput: "1 1 2 2 5", + recipeConfig: [ + { + "op": "MOD", + "args": [7, "Comma"] + } + ], + }, + { + name: "MOD: Line feed separated numbers", + input: "25\n13\n44\n7", + expectedOutput: "0 3 4 2", + recipeConfig: [ + { + "op": "MOD", + "args": [5, "Line feed"] + } + ], + }, + { + name: "MOD: Large numbers", + input: "123456789012345 987654321098765", + expectedOutput: "123456789012345 987654321098765", + recipeConfig: [ + { + "op": "MOD", + "args": [1234567890123456, "Space"] + } + ], + }, + { + name: "MOD: Mixed with non-numeric values", + input: "15 abc 4 def 7 xyz 23", + expectedOutput: "0 1 1 2", + recipeConfig: [ + { + "op": "MOD", + "args": [3, "Space"] + } + ], + }, + { + name: "MOD: Decimal numbers", + input: "10.5 15.7 8.2", + expectedOutput: "1.5 0.7 2.2", + recipeConfig: [ + { + "op": "MOD", + "args": [3, "Space"] + } + ], + }, + { + name: "MOD: Negative numbers", + input: "-15 -8 25 -10", + expectedOutput: "0 -2 1 -1", + recipeConfig: [ + { + "op": "MOD", + "args": [3, "Space"] + } + ], + }, + { + name: "MOD: Zero in input", + input: "0 5 10 15 20", + expectedOutput: "0 2 1 0 2", + recipeConfig: [ + { + "op": "MOD", + "args": [3, "Space"] + } + ], + }, + { + name: "MOD: Modulus of 2 (even/odd check)", + input: "1 2 3 4 5 6 7 8 9 10", + expectedOutput: "1 0 1 0 1 0 1 0 1 0", + recipeConfig: [ + { + "op": "MOD", + "args": [2, "Space"] + } + ], + }, + { + name: "MOD: Numbers with extra whitespace", + input: " 15 4 7 ", + expectedOutput: "0 1 1", + recipeConfig: [ + { + "op": "MOD", + "args": [3, "Space"] + } + ], + }, + { + name: "MOD: Empty input", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "MOD", + "args": [3, "Space"] + } + ], + }, + { + name: "MOD: Scientific notation", + input: "1e3 2e2 5e1", + expectedOutput: "1 2 2", + recipeConfig: [ + { + "op": "MOD", + "args": [3, "Space"] + } + ], + }, + { + name: "MOD: Floating point precision", + input: "10.123456789 20.987654321", + expectedOutput: "1.123456789 2.987654321", + recipeConfig: [ + { + "op": "MOD", + "args": [3, "Space"] + } + ], + }, + { + name: "MOD: Zero modulus error", + input: "15 4 7", + expectedError: true, + expectedOutput: "MOD - Modulus cannot be zero", + recipeConfig: [ + { + "op": "MOD", + "args": [0, "Space"] + } + ], + }, + { + name: "MOD: Semi-colon separated numbers", + input: "17;5;8;13", + expectedOutput: "2 0 3 3", + recipeConfig: [ + { + "op": "MOD", + "args": [5, "Semi-colon"] + } + ], + }, + { + name: "MOD: Colon separated numbers", + input: "25:9:14:7", + expectedOutput: "1 1 2 3", + recipeConfig: [ + { + "op": "MOD", + "args": [4, "Colon"] + } + ], + }, + { + name: "MOD: CRLF separated numbers", + input: "30\r\n18\r\n22\r\n11", + expectedOutput: "0 0 4 5", + recipeConfig: [ + { + "op": "MOD", + "args": [6, "CRLF"] + } + ], + }, +]); From f77ddf489050b339faa978bb28da954bafd289ae Mon Sep 17 00:00:00 2001 From: GCHQDeveloper581 <63102987+GCHQDeveloper581@users.noreply.github.com> Date: Fri, 24 Jul 2026 07:42:55 +0100 Subject: [PATCH 18/25] Fix pretty recipe parser ReDoS (#2687) Co-authored-by: zainnadeem(RedOpsCell) (main author) --- src/core/Utils.mjs | 48 ++++++++++++++++++++++++ tests/node/tests/Utils.mjs | 77 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index eae86374..cb641e3d 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -1015,6 +1015,7 @@ class Utils { // Parse bespoke recipe format recipe = recipe.replace(/\n/g, ""); + Utils._validatePrettyRecipe(recipe); let m, args; const recipeRegex = /([^(]+)\(((?:'[^'\\]*(?:\\.[^'\\]*)*'|[^)/'])*)(\/[^)]+)?\)/g, recipeConfig = []; @@ -1040,6 +1041,53 @@ class Utils { } + /** + * Performs a linear structural validation pass over pretty recipe syntax. + * + * @param {string} recipe + * @throws {Error} if the recipe is structurally invalid + */ + static _validatePrettyRecipe(recipe) { + let i = 0; + + while (i < recipe.length) { + const openParen = recipe.indexOf("(", i); + if (openParen === -1 || openParen === i) { + throw new Error("Invalid recipe"); + } + + i = openParen + 1; + let inString = false, + escaped = false, + foundCloseParen = false; + + for (; i < recipe.length; i++) { + const c = recipe[i]; + + if (inString) { + if (escaped) { + escaped = false; + } else if (c === "\\") { + escaped = true; + } else if (c === "'") { + inString = false; + } + } else if (c === "'") { + inString = true; + } else if (c === ")") { + foundCloseParen = true; + i++; + break; + } + } + + if (!foundCloseParen || inString || escaped) { + throw new Error("Invalid recipe"); + } + } + } + + /** * Formats a list of files or directories. * diff --git a/tests/node/tests/Utils.mjs b/tests/node/tests/Utils.mjs index 5ee7c936..ff4ffd96 100644 --- a/tests/node/tests/Utils.mjs +++ b/tests/node/tests/Utils.mjs @@ -26,4 +26,81 @@ TestRegister.addApiTests([ "\x7e...", ); }), + + it("Utils: should parse normal pretty recipes", () => { + assert.deepStrictEqual( + Utils.parseRecipeConfig("From_Base64('A-Za-z0-9+/=',true)To_Hex('Space')"), + [ + { + op: "From Base64", + args: ["A-Za-z0-9+/=", true], + }, + { + op: "To Hex", + args: ["Space"], + }, + ], + ); + }), + + it("Utils: should parse pretty recipe options", () => { + assert.deepStrictEqual( + Utils.parseRecipeConfig("A(/disabled/breakpoint)"), + [ + { + op: "A", + args: [], + disabled: true, + breakpoint: true, + }, + ], + ); + }), + + it("Utils: should parse escaped quotes and backslashes in pretty recipes", () => { + assert.deepStrictEqual( + Utils.parseRecipeConfig("A('\\'\\\\')"), + [ + { + op: "A", + args: ["'\\"], + }, + ], + ); + }), + + it("Utils: should parse large valid quoted pretty recipe arguments", () => { + const value = "x".repeat(10000); + + assert.deepStrictEqual( + Utils.parseRecipeConfig(`A('${value}')`), + [ + { + op: "A", + args: [value], + }, + ], + ); + }), + + it("Utils: should reject malformed pretty recipes with unmatched quotes", () => { + assert.throws( + () => Utils.parseRecipeConfig("A(" + "'".repeat(10000)), + /Invalid recipe/, + ); + }), + + it("Utils: should reject malformed pretty recipes with malformed parentheses", () => { + assert.throws( + () => Utils.parseRecipeConfig("A("), + /Invalid recipe/, + ); + }), + + it("Utils: should reject malformed pretty recipes with malformed escapes", () => { + assert.throws( + () => Utils.parseRecipeConfig("A('" + "\\".repeat(10000)), + /Invalid recipe/, + ); + }), ]); From d24ba1afce2e3a080308b5df7db033332fe94a1a Mon Sep 17 00:00:00 2001 From: GCHQDeveloper581 <63102987+GCHQDeveloper581@users.noreply.github.com> Date: Fri, 24 Jul 2026 08:05:09 +0100 Subject: [PATCH 19/25] Bump v11.3.0 (#2688) --- CHANGELOG.md | 148 +++++++++++++++++++++++++++++++++++++++++++++- package-lock.json | 4 +- package.json | 2 +- 3 files changed, 150 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77a55714..79786716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,75 @@ All major and minor version changes will be documented in this file. Details of ## Details +### [11.3.0] - 2026-07-24 +This release includes a security fix ([#2687]) +- Security: Fix pretty recipe parser ReDoS [@zainnadeem786] | [#2687] +- feat: add modulo operation [@thomasnemer] [@GCHQDeveloper581] | [#2103] +- Add HMAC regression tests for Decimal key parsing [@alleria173] | [#2680] +- fix: await Node API operations whose run() returns a non-async Promise [@roberson-io] | [#2659] +- chore (deps): bump morgan from 1.10.1 to 1.11.0 | [#2676] +- fix: fromDecimal Auto delimiter now correctly parses multiple numbers [@min23asdw] | [#2270] +- Add Generate Prime Number operation [@p-leriche] | [#2212] +- Add Modular Inverse operation [@p-leriche] | [#2207] +- Consolidate HTML entity tables into a single spec-generated source (#2645) [@roberson-io] | [#2671] +- Add Extended GCD operation [@p-leriche] | [#2206] +- Add COBS encoding/decoding operations [@giesmininkas] | [#2185] +- chore (deps): bump websocket-driver from 0.7.4 to 0.7.5 | [#2673] +- fix: remove stray punctuation from malformed To HTML Entity table values [@roberson-io] | [#2660] +- chore (deps): bump the actions-dependencies group across 1 directory with 6 updates | [#2668] +- chore (deps): bump the minor-updates group across 1 directory with 3 updates | [#2669] +- chore (deps): bump the patch-updates group with 5 updates | [#2654] +- feat: add TEA and XTEA block ciphers [@thomasxm] | [#2225] +- feat: add PRESENT and Twofish ciphers [@thomasxm] | [#2157] +- fix: support constructor and __proto__ parameters in Parse URI (#2578) [@mansiverma897993] | [#2581] +- feat: Implement automated option-type ingredient validation [@mansiverma897993] | [#2625] +- Add Ascon (NIST SP 800-232) operations: Hash, MAC, Encrypt, Decrypt [@thomasxm] | [#2155] +- chore (deps): bump the patch-updates group across 1 directory with 6 updates | [#2638] +- chore (deps): bump webpack from 5.107.2 to 5.108.3 in the minor-updates group | [#2635] +- chore (deps): bump nginxinc/nginx-unprivileged from `458ecbe` to `fd3314e` in the docker-dependencies group | [#2633] +- Feature: automatically expire PRs if CLA remains unsigned for an extended period [@GCHQDeveloper581] | [#2636] +- fix/2445 HOTP (and 2426 TOTP) type errors [@alleria173] | [#2620] +- Fix base32 unicode alphabet [@loki1205] | [#2380] +- Add a workflow to automatically flag PRs without a signed CLA [@GCHQDeveloper581] | [#2627] +- fix/2444 TOTP input validation for correct otpauth uri generation [@alleria173] | [#2621] +- Validate Wrap line width [@vetrovk] [@GCHQDeveloper581] [@C85297] | [#2606] +- Handle malformed image parser errors in View Bit Plane [@zainnadeem786] | [#2612] +- Fixes #2446 hotp otpauth uri validation [@alleria173] | [#2614] +- Handle invalid bcrypt salt errors in Bcrypt compare [@zainnadeem786] | [#2615] +- Validate empty Show On Map options [@vetrovk] | [#2631] +- Create AGENTS.md file [@C85297] | [#2619] +- Set parameter validation Metadata for GenerateImage operations [@GCHQDeveloper581] | [#2611] +- Update 4 vulnerable dependencies [@GCHQDeveloper581] | [#2616] +- Fix BigNumber deserialisation in Dish, and add tests [@GCHQDeveloper581] | [#2607] +- chore (deps): bump the docker-dependencies group with 2 updates | [#2600] +- chore (deps): bump the patch-updates group with 8 updates | [#2602] +- chore (deps): bump actions/checkout from 6.0.3 to 7.0.0 in the actions-dependencies group | [#2601] +- chore (deps): bump the minor-updates group with 2 updates | [#2603] +- Handle empty Generate Image mode [@vetrovk] | [#2598] +- Fix stale presenter after expected operation errors [@zainnadeem786] [@GCHQDeveloper581] | [#2589] +- Clean up/rationalise webpack paths and thereby increase compatibility for Win… [@GCHQDeveloper581] | [#2585] +- Improve parameter validation for a number of operations where exceptions otherwise caused. [@GCHQDeveloper581] | [#2586] +- Fix uncaught TypeError in "Show on map" operation. [@lzandman] | [#2453] +- fix: jsonata $base64decode/$base64encode in Web Worker [@min23asdw] | [#2275] +- fix Dechunk HTTP Response leaks terminating chunk and trailers into output [@williballenthin] | [#2290] +- fix: MIME Decoding corrupts non-ASCII characters in Base64-encoded words [@williballenthin] | [#2291] +- fix: Gzip comment with header checksum produces corrupt streams [@williballenthin] | [#2288] +- fix TLV Parser BER long-form length parsing [@williballenthin] | [#2289] +- fix: Unescape Unicode Characters accepts 4-6 hex digits for U+ prefix [@williballenthin] | [#2287] +- fix Set Difference and Set Intersection preserve duplicates from first sample [@williballenthin] | [#2286] +- fix: From Base operation produces wrong results for fractional inputs [@williballenthin] | [#2285] +- fix Median operation returns incorrect result for unsorted odd-length inputs [@williballenthin] | [#2284] +- Added RenderPDF functionality [@Shailendra1703] [@GCHQDeveloper581] | [#2105] +- Fix URL encoding incorrectly converting input to UTF-8 [@C85297] | [#2340] +- feat: Add automated parameter validation framework [@mansiverma897993] | [#2561] +- Fix: added viewport styles to img tag in RenderImage Dish [@Shailendra1703] [@C85297] | [#2109] +- chore (deps): bump form-data from 4.0.5 to 4.0.6 | [#2572] +- chore (deps): bump the patch-updates group with 5 updates [@GCHQDeveloper581] | [#2580] +- chore (deps): bump the docker-dependencies group with 2 updates | [#2579] +- Fix operation description rendering [@C85297] | [#2577] +- chore (deps): bump launch-editor from 2.13.1 to 2.14.1 | [#2574] +- chore (deps): bump dompurify from 3.4.8 to 3.4.9 | [#2573] + ### [11.2.0] - 2026-06-17 This release includes a security fix ([#2569]) - Security: Chart operation prototype protection [@C85297] | [#2569] @@ -718,6 +787,7 @@ Breaking changes: ## [4.0.0] - 2016-11-28 - Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306) +[11.3.0]: https://github.com/gchq/CyberChef/releases/tag/v11.3.0 [11.2.0]: https://github.com/gchq/CyberChef/releases/tag/v11.2.0 [11.1.0]: https://github.com/gchq/CyberChef/releases/tag/v11.1.0 [11.0.0]: https://github.com/gchq/CyberChef/releases/tag/v11.0.0 @@ -1018,6 +1088,17 @@ Breaking changes: [@qa2me]: https://github.com/qa2me [@heapframe]: https://github.com/heapframe [@skyswordw]: https://github.com/skyswordw +[@thomasnemer]: https://github.com/thomasnemer +[@alleria173]: https://github.com/alleria173 +[@roberson-io]: https://github.com/roberson-io +[@min23asdw]: https://github.com/min23asdw +[@giesmininkas]: https://github.com/giesmininkas +[@mansiverma897993]: https://github.com/mansiverma897993 +[@loki1205]: https://github.com/loki1205 +[@vetrovk]: https://github.com/vetrovk +[@zainnadeem786]: https://github.com/zainnadeem786 +[@williballenthin]: https://github.com/williballenthin +[@Shailendra1703]: https://github.com/Shailendra1703 [8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7 @@ -1389,4 +1470,69 @@ Breaking changes: [#2404]: https://github.com/gchq/CyberChef/pull/2404 [#2458]: https://github.com/gchq/CyberChef/pull/2458 [#2514]: https://github.com/gchq/CyberChef/pull/2514 - +[#2687]: https://github.com/gchq/CyberChef/pull/2687 +[#2103]: https://github.com/gchq/CyberChef/pull/2103 +[#2680]: https://github.com/gchq/CyberChef/pull/2680 +[#2659]: https://github.com/gchq/CyberChef/pull/2659 +[#2676]: https://github.com/gchq/CyberChef/pull/2676 +[#2270]: https://github.com/gchq/CyberChef/pull/2270 +[#2212]: https://github.com/gchq/CyberChef/pull/2212 +[#2207]: https://github.com/gchq/CyberChef/pull/2207 +[#2671]: https://github.com/gchq/CyberChef/pull/2671 +[#2206]: https://github.com/gchq/CyberChef/pull/2206 +[#2185]: https://github.com/gchq/CyberChef/pull/2185 +[#2673]: https://github.com/gchq/CyberChef/pull/2673 +[#2660]: https://github.com/gchq/CyberChef/pull/2660 +[#2668]: https://github.com/gchq/CyberChef/pull/2668 +[#2669]: https://github.com/gchq/CyberChef/pull/2669 +[#2654]: https://github.com/gchq/CyberChef/pull/2654 +[#2225]: https://github.com/gchq/CyberChef/pull/2225 +[#2157]: https://github.com/gchq/CyberChef/pull/2157 +[#2581]: https://github.com/gchq/CyberChef/pull/2581 +[#2625]: https://github.com/gchq/CyberChef/pull/2625 +[#2155]: https://github.com/gchq/CyberChef/pull/2155 +[#2638]: https://github.com/gchq/CyberChef/pull/2638 +[#2635]: https://github.com/gchq/CyberChef/pull/2635 +[#2633]: https://github.com/gchq/CyberChef/pull/2633 +[#2636]: https://github.com/gchq/CyberChef/pull/2636 +[#2620]: https://github.com/gchq/CyberChef/pull/2620 +[#2380]: https://github.com/gchq/CyberChef/pull/2380 +[#2627]: https://github.com/gchq/CyberChef/pull/2627 +[#2621]: https://github.com/gchq/CyberChef/pull/2621 +[#2606]: https://github.com/gchq/CyberChef/pull/2606 +[#2612]: https://github.com/gchq/CyberChef/pull/2612 +[#2614]: https://github.com/gchq/CyberChef/pull/2614 +[#2615]: https://github.com/gchq/CyberChef/pull/2615 +[#2631]: https://github.com/gchq/CyberChef/pull/2631 +[#2619]: https://github.com/gchq/CyberChef/pull/2619 +[#2611]: https://github.com/gchq/CyberChef/pull/2611 +[#2616]: https://github.com/gchq/CyberChef/pull/2616 +[#2607]: https://github.com/gchq/CyberChef/pull/2607 +[#2600]: https://github.com/gchq/CyberChef/pull/2600 +[#2602]: https://github.com/gchq/CyberChef/pull/2602 +[#2601]: https://github.com/gchq/CyberChef/pull/2601 +[#2603]: https://github.com/gchq/CyberChef/pull/2603 +[#2598]: https://github.com/gchq/CyberChef/pull/2598 +[#2589]: https://github.com/gchq/CyberChef/pull/2589 +[#2585]: https://github.com/gchq/CyberChef/pull/2585 +[#2586]: https://github.com/gchq/CyberChef/pull/2586 +[#2453]: https://github.com/gchq/CyberChef/pull/2453 +[#2275]: https://github.com/gchq/CyberChef/pull/2275 +[#2290]: https://github.com/gchq/CyberChef/pull/2290 +[#2291]: https://github.com/gchq/CyberChef/pull/2291 +[#2288]: https://github.com/gchq/CyberChef/pull/2288 +[#2289]: https://github.com/gchq/CyberChef/pull/2289 +[#2287]: https://github.com/gchq/CyberChef/pull/2287 +[#2286]: https://github.com/gchq/CyberChef/pull/2286 +[#2285]: https://github.com/gchq/CyberChef/pull/2285 +[#2284]: https://github.com/gchq/CyberChef/pull/2284 +[#2105]: https://github.com/gchq/CyberChef/pull/2105 +[#2340]: https://github.com/gchq/CyberChef/pull/2340 +[#2561]: https://github.com/gchq/CyberChef/pull/2561 +[#2109]: https://github.com/gchq/CyberChef/pull/2109 +[#2572]: https://github.com/gchq/CyberChef/pull/2572 +[#2580]: https://github.com/gchq/CyberChef/pull/2580 +[#2579]: https://github.com/gchq/CyberChef/pull/2579 +[#2577]: https://github.com/gchq/CyberChef/pull/2577 +[#2574]: https://github.com/gchq/CyberChef/pull/2574 +[#2573]: https://github.com/gchq/CyberChef/pull/2573 diff --git a/package-lock.json b/package-lock.json index a8068e3f..1ea35a56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "11.2.0", + "version": "11.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "11.2.0", + "version": "11.3.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 8512412b..c4f9a7fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "11.2.0", + "version": "11.3.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "GCHQ ", "homepage": "https://gchq.github.io/CyberChef", From 4b43f89eb8aace40da2693caac5e64ea9223a27a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 08:38:40 +0100 Subject: [PATCH 20/25] chore (deps): bump nginxinc/nginx-unprivileged from `fd3314e` to `44e3633` in the docker-dependencies group (#2684) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 564e8d66..e7076f42 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ RUN npm run build ######################################### # Package static build files into nginx # ######################################### -FROM nginxinc/nginx-unprivileged:stable-alpine@sha256:fd3314e343bad2de4e1127ef58be122abbfa7e09572fa46ae62fcddb6b3f21c5 AS cyberchef +FROM nginxinc/nginx-unprivileged:stable-alpine@sha256:44e36330f74d4f3a1d4e222acca9e23b401fb87811a7597024502bb759c4dd49 AS cyberchef LABEL maintainer="GCHQ " From 51600c4a4f2130581870d94c71bdc63c40b06996 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 08:49:31 +0100 Subject: [PATCH 21/25] chore (deps): bump the actions-dependencies group across 1 directory with 2 updates (#2685) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/master.yml | 4 ++-- .github/workflows/pull_requests.yml | 4 ++-- .github/workflows/releases.yml | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index e8a120ed..f3c6f711 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -16,10 +16,10 @@ jobs: pages: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set node version - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index aee3d22a..5ee27086 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -12,10 +12,10 @@ jobs: main: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set node version - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index ae92cdf9..81e1ae1c 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -22,10 +22,10 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set node version - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 registry-url: "https://registry.npmjs.org" @@ -110,10 +110,10 @@ jobs: needs: main runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set node version - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 registry-url: "https://registry.npmjs.org" From ed21930260c10cd6d89bbfd0532d5fe46bd83647 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:04:47 +0100 Subject: [PATCH 22/25] chore (deps): bump the patch-updates group across 1 directory with 9 updates (#2686) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 140 +++++++++++++++++++++++----------------------- package.json | 18 +++--- 2 files changed, 79 insertions(+), 79 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1ea35a56..19b8d15b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "d3": "7.9.0", "d3-hexbin": "^0.2.2", "diff": "^9.0.0", - "dompurify": "^3.4.11", + "dompurify": "^3.4.12", "es6-promisify": "^7.0.0", "escodegen": "^2.1.0", "esprima": "^4.0.1", @@ -59,7 +59,7 @@ "js-sha3": "^0.9.3", "jsesc": "^3.1.0", "json5": "^2.2.3", - "jsonata": "^2.2.1", + "jsonata": "^2.2.2", "jsonpath-plus": "^10.4.0", "jsonwebtoken": "9.0.3", "jsqr": "^1.4.0", @@ -74,7 +74,7 @@ "lz4js": "^0.2.0", "markdown-it": "^14.3.0", "moment": "^2.30.1", - "moment-timezone": "^0.6.2", + "moment-timezone": "^0.6.3", "ngeohash": "^0.6.3", "node-forge": "^1.4.0", "node-md6": "^0.1.0", @@ -86,7 +86,7 @@ "path": "^0.12.7", "popper.js": "^1.16.1", "process": "^0.11.10", - "protobufjs": "^8.7.0", + "protobufjs": "^8.7.1", "punycode.js": "^2.3.1", "qr-image": "^3.2.0", "reflect-metadata": "^0.2.2", @@ -121,10 +121,10 @@ "@codemirror/state": "^6.7.1", "@codemirror/view": "^6.43.6", "@puppeteer/browsers": "3.0.6", - "autoprefixer": "^10.5.2", + "autoprefixer": "^10.5.4", "babel-loader": "^10.1.1", "base64-loader": "^1.0.0", - "chromedriver": "^150.0.3", + "chromedriver": "^150.0.4", "cli-progress": "^3.12.0", "colors": "^1.4.0", "compression-webpack-plugin": "^12.0.0", @@ -132,7 +132,7 @@ "core-js": "^3.49.0", "cspell": "^10.0.1", "css-loader": "^7.1.4", - "eslint": "^9.39.4", + "eslint": "^9.39.5", "eslint-plugin-jsdoc": "^50.8.0", "globals": "^17.7.0", "grunt": "^1.6.2", @@ -151,7 +151,7 @@ "mini-css-extract-plugin": "2.10.2", "modify-source-webpack-plugin": "^4.1.0", "nightwatch": "^3.16.0", - "postcss": "^8.5.16", + "postcss": "^8.5.21", "postcss-css-variables": "^0.19.0", "postcss-import": "^16.1.1", "postcss-loader": "^8.2.1", @@ -159,7 +159,7 @@ "sitemap": "^9.0.1", "terser": "^5.49.0", "webpack": "^5.108.4", - "webpack-bundle-analyzer": "^5.3.0", + "webpack-bundle-analyzer": "^5.3.1", "webpack-dev-server": "^5.2.6", "webpack-node-externals": "^3.0.0", "worker-loader": "^3.0.8" @@ -2776,9 +2776,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", "dev": true, "license": "MIT", "dependencies": { @@ -2788,7 +2788,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -2813,9 +2813,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, "license": "MIT", "engines": { @@ -5620,9 +5620,9 @@ "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.5.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.2.tgz", - "integrity": "sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==", + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", "dev": true, "funding": [ { @@ -5640,8 +5640,8 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.4", - "caniuse-lite": "^1.0.30001799", + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -5842,9 +5842,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.40", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", - "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.1.tgz", + "integrity": "sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6376,9 +6376,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", "dev": true, "funding": [ { @@ -6396,10 +6396,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -6591,9 +6591,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "dev": true, "funding": [ { @@ -6733,9 +6733,9 @@ } }, "node_modules/chromedriver": { - "version": "150.0.3", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-150.0.3.tgz", - "integrity": "sha512-i2L979d6YDTVVUDUPWXz75HGKKVhjNXo74gLiy/f8Adb5zHLU+h3ABdg8RRoiegtjJB0m9vUEiPTdecD0ifa3w==", + "version": "150.0.4", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-150.0.4.tgz", + "integrity": "sha512-Yg2oXBONi1mqAMOG2ybwUneSvxIn04NF4qNAJ+yGO2H7WwX1WYLGQRHVHsbySEjIuqIyDmS1ZfAw6p0wtl43zQ==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -8702,9 +8702,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.11", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", - "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -8809,9 +8809,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.379", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.379.tgz", - "integrity": "sha512-v/qV5aV5EUA2pGilzUCq5/eyOloZAqDZBu9UMBIzgPpLlprjSR6zswsWBTv0KpqxLGUAZEwhO95ZCt7srymNVA==", + "version": "1.5.396", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.396.tgz", + "integrity": "sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==", "dev": true, "license": "ISC" }, @@ -9112,9 +9112,9 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", "dev": true, "license": "MIT", "dependencies": { @@ -9123,8 +9123,8 @@ "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -12535,9 +12535,9 @@ } }, "node_modules/jsonata": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jsonata/-/jsonata-2.2.1.tgz", - "integrity": "sha512-xd1uwUrKeIcJbsWhaoS3qAX4Ea8m0Mw0G5nlnAQvPT7TbZ5qaPdzBVTQia9KfyuyQm+nenfyjvzUDTRYHsC2sw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/jsonata/-/jsonata-2.2.2.tgz", + "integrity": "sha512-XDFH2PuaAXv0AXJEWwElXbqADmKFGKMLMkFb+qOz0EhjQW2EIJ6pgtordLU+4u3IVERyBfqy6bi6kZKEncvRqA==", "license": "MIT", "engines": { "node": ">= 8" @@ -13584,9 +13584,9 @@ } }, "node_modules/moment-timezone": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.6.2.tgz", - "integrity": "sha512-lDsQv8FoGdBUdf0+TjGsq2orxKuXdwFlQ6Zw6TX3xIcTwTfEpCLyKqvEauvCHJ8iu3KBV8+uPhlv70YsNGdUBQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.6.3.tgz", + "integrity": "sha512-pVEPA/HCFHHbwJ130ywnzYuZpkEGcP6Daa/OwNebpA18MybeFHmQilAGGovXgWijQ8vQtmud9jZrziUBgsykfg==", "license": "MIT", "dependencies": { "moment": "^2.29.4" @@ -13698,9 +13698,9 @@ "license": "ISC" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -14033,9 +14033,9 @@ "license": "CC0-1.0" }, "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "dev": true, "license": "MIT", "engines": { @@ -15041,9 +15041,9 @@ } }, "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.21.tgz", + "integrity": "sha512-v4sDNP3fdNiWMfabO7OwOQdOX8TiQSztKyT1Wj0w+j7LDallJThJRBBBmzVGyYj0crMh7jlV4zepPkiNu9UwDQ==", "dev": true, "funding": [ { @@ -15061,7 +15061,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -15310,9 +15310,9 @@ "license": "MIT" }, "node_modules/protobufjs": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.7.0.tgz", - "integrity": "sha512-uu52JNxLh3vsL7tXU/h0gDaywufvuUCTbGSi0NKQKBZ2ZopkmrWQJSQO/EFqzu/5YhiwgVM8rq/a/iVpx4eZ0g==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.7.1.tgz", + "integrity": "sha512-agdGHrXNTv0IrYscJPDou/PlEJk1c/hBZ9o/B5NH2i/nSPtPqacNxzgwf1CebXxFMjMrZH5sqv9uQuw96aGt/A==", "license": "BSD-3-Clause", "dependencies": { "long": "^5.3.2" @@ -18212,9 +18212,9 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-5.3.0.tgz", - "integrity": "sha512-PEhAoqiJ+47d0uLMx/+zo5XOvaU+Vk6N2ZLht7H3n09QLy/fhyvqGNwjdRUHJDgMN8crBR2ZwVHkIswT3Xuawg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-5.3.1.tgz", + "integrity": "sha512-wP2EusncRGL1tZyMHC/umLkjPdYMkTL9nPEKh8G8dkYCJ9TyF6xnXFqjhdqmv5J900irN/g0P5jMvLT22krEXQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c4f9a7fb..d9748d74 100644 --- a/package.json +++ b/package.json @@ -50,10 +50,10 @@ "@codemirror/state": "^6.7.1", "@codemirror/view": "^6.43.6", "@puppeteer/browsers": "3.0.6", - "autoprefixer": "^10.5.2", + "autoprefixer": "^10.5.4", "babel-loader": "^10.1.1", "base64-loader": "^1.0.0", - "chromedriver": "^150.0.3", + "chromedriver": "^150.0.4", "cli-progress": "^3.12.0", "colors": "^1.4.0", "compression-webpack-plugin": "^12.0.0", @@ -61,7 +61,7 @@ "core-js": "^3.49.0", "cspell": "^10.0.1", "css-loader": "^7.1.4", - "eslint": "^9.39.4", + "eslint": "^9.39.5", "eslint-plugin-jsdoc": "^50.8.0", "globals": "^17.7.0", "grunt": "^1.6.2", @@ -80,7 +80,7 @@ "mini-css-extract-plugin": "2.10.2", "modify-source-webpack-plugin": "^4.1.0", "nightwatch": "^3.16.0", - "postcss": "^8.5.16", + "postcss": "^8.5.21", "postcss-css-variables": "^0.19.0", "postcss-import": "^16.1.1", "postcss-loader": "^8.2.1", @@ -88,7 +88,7 @@ "sitemap": "^9.0.1", "terser": "^5.49.0", "webpack": "^5.108.4", - "webpack-bundle-analyzer": "^5.3.0", + "webpack-bundle-analyzer": "^5.3.1", "webpack-dev-server": "^5.2.6", "webpack-node-externals": "^3.0.0", "worker-loader": "^3.0.8" @@ -123,7 +123,7 @@ "d3": "7.9.0", "d3-hexbin": "^0.2.2", "diff": "^9.0.0", - "dompurify": "^3.4.11", + "dompurify": "^3.4.12", "es6-promisify": "^7.0.0", "escodegen": "^2.1.0", "esprima": "^4.0.1", @@ -143,7 +143,7 @@ "js-sha3": "^0.9.3", "jsesc": "^3.1.0", "json5": "^2.2.3", - "jsonata": "^2.2.1", + "jsonata": "^2.2.2", "jsonpath-plus": "^10.4.0", "jsonwebtoken": "9.0.3", "jsqr": "^1.4.0", @@ -158,7 +158,7 @@ "lz4js": "^0.2.0", "markdown-it": "^14.3.0", "moment": "^2.30.1", - "moment-timezone": "^0.6.2", + "moment-timezone": "^0.6.3", "ngeohash": "^0.6.3", "node-forge": "^1.4.0", "node-md6": "^0.1.0", @@ -170,7 +170,7 @@ "path": "^0.12.7", "popper.js": "^1.16.1", "process": "^0.11.10", - "protobufjs": "^8.7.0", + "protobufjs": "^8.7.1", "punycode.js": "^2.3.1", "qr-image": "^3.2.0", "reflect-metadata": "^0.2.2", From 08b2b7b3dd6032989259388118bff8ad4c40ebff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:05:16 +0100 Subject: [PATCH 23/25] chore (deps): bump shell-quote from 1.8.4 to 1.10.0 (#2690) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19b8d15b..ba965539 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16616,9 +16616,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", - "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", "dev": true, "license": "MIT", "engines": { From 536ee840345da23a81e0164063bf711d505910f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:54:29 +0100 Subject: [PATCH 24/25] chore (deps): bump assorted vulnerable dependencies (#2689) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: GCHQDeveloper581 <63102987+GCHQDeveloper581@users.noreply.github.com> --- package-lock.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba965539..940f4086 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6024,9 +6024,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "dev": true, "license": "MIT", "dependencies": { @@ -6259,9 +6259,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -9692,9 +9692,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "dev": true, "funding": [ { @@ -9772,9 +9772,9 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -13497,9 +13497,9 @@ } }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -15760,9 +15760,9 @@ } }, "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { From c7d20d86017b7a1ff99c53a5e007a041f92c9671 Mon Sep 17 00:00:00 2001 From: Zain Nadeem Date: Fri, 24 Jul 2026 19:47:02 +0500 Subject: [PATCH 25/25] Add npm allowScripts policy for npm v12 (#2682) --- package.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/package.json b/package.json index d9748d74..f8c2961e 100644 --- a/package.json +++ b/package.json @@ -193,6 +193,14 @@ "xregexp": "^5.1.2", "zlibjs": "^0.3.1" }, + "allowScripts": { + "@nightwatch/nightwatch-inspector@1.0.1": true, + "chromedriver@150.0.3": true, + "core-js": false, + "core-js-pure": false, + "fsevents": false, + "tesseract.js": false + }, "scripts": { "start": "npx grunt dev", "build": "npx grunt prod",