Add To Base26 and From Base26 operations

Implements Base26 encoding/decoding using uppercase A-Z as digits
(A=0, B=1, ..., Z=25). Input bytes are interpreted as a big-endian
unsigned integer and converted to/from radix 26.

Includes tests for the reference vector (EAT -> JYGBS), round-trips,
empty input, zero bytes, and binary data.

Closes gchq/CyberChef#1511
This commit is contained in:
NOVA-Openclaw 2026-07-26 22:33:34 +00:00
parent c56dd23358
commit 3b3eb2f78e
4 changed files with 270 additions and 0 deletions

View File

@ -30,6 +30,8 @@
"From Bech32", "From Bech32",
"To Base62", "To Base62",
"From Base62", "From Base62",
"To Base26",
"From Base26",
"To Base64", "To Base64",
"From Base64", "From Base64",
"Show Base64 offsets", "Show Base64 offsets",

View File

@ -0,0 +1,61 @@
/**
* @author NOVA-Openclaw
* @copyright Crown Copyright 2026
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
/**
* From Base26 operation
*/
class FromBase26 extends Operation {
/**
* FromBase26 constructor
*/
constructor() {
super();
this.name = "From Base26";
this.module = "Default";
this.description = "Base26 is a notation for encoding arbitrary byte data using the uppercase letters A-Z. The input string is interpreted as a radix-26 number using A=0, B=1, ..., Z=25, and converted back to its big-endian byte representation.";
this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems";
this.inputType = "string";
this.outputType = "byteArray";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
if (input.length < 1) return [];
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const re = new RegExp("[^A-Za-z]", "g");
input = input.replace(re, "").toUpperCase();
if (input.length < 1) return [];
let number = 0n;
for (const c of input) {
number = number * 26n + BigInt(ALPHABET.indexOf(c));
}
if (number === 0n) return [0];
const bytes = [];
while (number > 0n) {
bytes.unshift(Number(number & 0xFFn));
number >>= 8n;
}
return bytes;
}
}
export default FromBase26;

View File

@ -0,0 +1,58 @@
/**
* @author NOVA-Openclaw
* @copyright Crown Copyright 2026
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
/**
* To Base26 operation
*/
class ToBase26 extends Operation {
/**
* ToBase26 constructor
*/
constructor() {
super();
this.name = "To Base26";
this.module = "Default";
this.description = "Base26 is a notation for encoding arbitrary byte data using the uppercase letters A-Z. The input bytes are interpreted as a big-endian unsigned integer, which is then represented in radix 26 using A=0, B=1, ..., Z=25.";
this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = new Uint8Array(input);
if (input.length < 1) return "";
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let number = 0n;
for (const byte of input) {
number = (number << 8n) | BigInt(byte);
}
if (number === 0n) return ALPHABET[0];
let output = "";
while (number > 0n) {
output = ALPHABET[Number(number % 26n)] + output;
number /= 26n;
}
return output;
}
}
export default ToBase26;

View File

@ -0,0 +1,149 @@
/**
* Base26 tests.
*
* @author NOVA-Openclaw
*
* @copyright Crown Copyright 2026
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "To Base26: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "To Base26",
args: [],
},
],
},
{
name: "To Base26: EAT -> JYGBS",
input: "EAT",
expectedOutput: "JYGBS",
recipeConfig: [
{
op: "To Base26",
args: [],
},
],
},
{
name: "To Base26: Hello, World!",
input: "Hello, World!",
expectedOutput: "LBVLPVLUNRXHXRHSZIHQLL",
recipeConfig: [
{
op: "To Base26",
args: [],
},
],
},
{
name: "To Base26: UTF-8",
input: "ნუ პანიკას",
expectedOutput: "HLPHPKXWXPTREJHEBQENHKRQAKLFKOWIWEXTDNAOPRQFRQLL",
recipeConfig: [
{
op: "To Base26",
args: [],
},
],
},
{
name: "To Base26: zero byte",
input: "\x00",
expectedOutput: "A",
recipeConfig: [
{
op: "To Base26",
args: [],
},
],
},
{
name: "To Base26: binary",
input: "\x01\x02\x03",
expectedOutput: "DTSL",
recipeConfig: [
{
op: "To Base26",
args: [],
},
],
},
{
name: "From Base26: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "From Base26",
args: [],
},
],
},
{
name: "From Base26: JYGBS -> EAT",
input: "JYGBS",
expectedOutput: "EAT",
recipeConfig: [
{
op: "From Base26",
args: [],
},
],
},
{
name: "From Base26: lowercase and ignored characters",
input: "j y-g!b@s",
expectedOutput: "EAT",
recipeConfig: [
{
op: "From Base26",
args: [],
},
],
},
{
name: "From Base26: A -> zero byte",
input: "A",
expectedOutput: "\x00",
recipeConfig: [
{
op: "From Base26",
args: [],
},
],
},
{
name: "From Base26: DTSL -> binary",
input: "DTSL",
expectedOutput: "\x01\x02\x03",
recipeConfig: [
{
op: "From Base26",
args: [],
},
],
},
{
name: "To Base26 round-trip",
input: "The quick brown fox jumps over the lazy dog.",
expectedOutput: "The quick brown fox jumps over the lazy dog.",
recipeConfig: [
{
op: "To Base26",
args: [],
},
{
op: "From Base26",
args: [],
},
],
}
]);