// Generated by tools/build-web.py from typescript/{alphabet,sigilcode}.ts. (() => { 'use strict'; // Generated by alphabet/alphabet.py from Unicode 14.0.0's data; do not edit. const ALPHABET = "0123456789abcdefghijknopqrstuvwxyzðøþđħĸłŧƀƃƈƌƒƕƙƚƛƞƣƥƨƪƫƭƴƶƹƺƻƾǂǃǝǥȡȥȴȵȶȷȸȹȼȿɀɂɇɉɋɍɏɐɒɓɔɖɗɘɚɛɜɝɞɟɠɢɤɥɦɧɨɫɬɭɮɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀʁʂʄʅʆʇʈʉʊʌʍʎʐʓʔʕʖʗʘʙʚʛʜʝʞʟʠʡʢʤʥʧʨʩʬʭʮʯͱͳͷͼͽβδζθλμξπτχψωϗϙϛϝϟϡϻϼджлцчшщъыэюяђљњџѥѧѩѫѭѯѹѻѽѿҁҋҏґҕҗҙқҝҟҡңҥҧҩҫҭҳҵҷҹҿӄӆӈӎӷӻӽӿԃԅԇԉԋԏԓԕԗԙԟԡԣԥሀሁሂሃሄህሆሇለሉሊላሌልሎሏሐሑሒሓሔሕሖሗመሙሚማሜምሞሟሠሡሢሤሥሦሧረሩሪራሬርሮሯሰሱሲሳሴስሶሷሸሹሺሻሼሽሾሿቀቁቂቃቄቅቆቇቈቊቋቌቍቐቑቒቓቔቕቖቘቚቛቜቝበቡቢባቤብቦቧቨቩቪቫቬቭቮቯተቱቲታቴትቶቷቸቹቺቻቼችቾቿኀኁኂኃኄኅኆኇኈኊኋኌኍነኑኒናኔንኖኗኘኙኚኛኜኝኞኟአኡኢኣኤእኦኧከኩኪካኬክኮኯኰኲኳኴኵኸኹኺኻኼኽኾዀዂዃዄዅወዉዊዋዌውዎዏዑዒዓዔዕዖዘዙዚዛዜዝዞዟዠዡዢዣዤዥዦዧየዩዪያዬይዮዯደዱዲዳዴድዶዷዸዹዺዻዼዽዾዿጀጁጂጃጄጅጆጇገጉጊጋጌግጎጏጐጒጓጔጕጘጙጚጛጜጝጞጟጠጡጢጣጤጥጦጧጨጩጪጫጬጭጮጯጰጱጲጳጴጵጶጷጸጹጺጻጼጽጾጿፀፁፂፃፄፅፆፇፈፉፊፋፌፍፎፏፐፑፒፓፔፕፖፗፘፙፚᎀᎁᎂᎃᎄᎅᎆᎇᎈᎉᎊᎋᎌᎍᎎᎏᐁᐂᐃᐄᐅᐆᐇᐈᐉᐊᐋᐌᐍᐎᐏᐐᐑᐒᐓᐔᐕᐖᐗᐘᐙᐚᐛᐜᐝᐟᐡᐢᐣᐤᐥᐦᐨᐩᐪᐫᐬᐭᐮᐯᐰᐱᐲᐳᐴᐵᐶᐷᐸᐹᐺᐻᐼᐽᐾᐿᑁᑂᑃᑄᑅᑆᑇᑈᑉᑋᑍᑏᑐᑑᑒᑓᑔᑕᑖᑗᑘᑙᑚᑛᑜᑞᑟᑠᑡᑢᑣᑤᑥᑦᑧᑨᑩᑪᑫᑬᑭᑮᑰᑱᑳᑴᑵᑶᑷᑸᑹᑺᑻᑼᑽᑾᑿᒀᒁᒂᒃᒄᒅᒆᒇᒈᒉᒊᒋᒌᒍᒎᒏᒐᒑᒒᒓᒔᒕᒖᒗᒘᒙᒚᒛᒜᒝᒞᒟᒠᒡᒢᒣᒤᒥᒦᒧᒨᒩᒪᒫᒬᒭᒮᒯᒰᒱᒲᒳᒴᒵᒶᒷᒸᒹᒺᒻᒼᒽᒾᓀᓁᓂᓃᓄᓅᓆᓇᓈᓉᓊᓋᓌᓍᓎᓏᓐᓒᓓᓔᓕᓖᓗᓘᓙᓚᓛᓜᓝᓞᓟᓠᓡᓢᓣᓤᓥᓦᓧᓨᓩᓪᓫᓬᓭᓮᓯᓰᓱᓲᓳᓴᓵᓶᓷᓸᓹᓺᓻᓼᓽᓾᓿᔀᔁᔂᔃᔄᔅᔆᔇᔈᔉᔊᔌᔍᔎᔏᔐᔑᔒᔓᔔᔕᔖᔗᔘᔙᔚᔛᔜᔝᔞᔟᔠᔡᔢᔣᔤᔥᔦᔧᔨᔩᔪᔫᔬᔭᔮᔯᔰᔱᔲᔳᔴᔵᔶᔷᔸᔹᔺᔻᔼᔽᔾᔿᕂᕃᕄᕅᕆᕇᕈᕉᕊᕋᕌᕍᕎᕏᕑᕒᕓᕔᕕᕖᕗᕘᕙᕚᕛᕜᕝᕞᕟᕠᕡᕢᕣᕤᕥᕦᕧᕨᕩᕪᕫᕬᕭᕮᕯᕰᕱᕲᕳᕴᕵᕶᕸᕹᕺᕻᕼᕾᕿᖀᖁᖂᖃᖄᖅᖆᖇᖈᖉᖊᖋᖌᖍᖎᖏᖐᖑᖒᖓᖔᖗᖘᖙᖚᖛᖜᖝᖞᖟᖠᖡᖢᖣᖤᖥᖦᖧᖨᖩᖪᖫᖬᖭᖮᖰᖱᖲᖳᖴᖵᖶᖷᖸᖹᖺᖻᖼᖽᖾᖿᗀᗁᗂᗃᗄᗅᗆᗇᗈᗉᗊᗋᗌᗍᗎᗏᗐᗑᗒᗓᗔᗕᗖᗗᗘᗙᗚᗛᗜᗝᗞᗟᗠᗡᗢᗣᗤᗥᗦᗧᗨᗩᗫᗬᗭᗮᗯᗰᗱᗲᗳᗴᗵᗶᗷᗸᗹᗺᗻᗼᗽᗾᗿᘀᘁᘅᘆᘈᘉᘊᘋᘌᘍᘎᘏᘐᘑᘒᘓᘔᘕᘖᘗᘘᘙᘚᘛᘜᘝᘞᘟᘠᘡᘥᘦᘧᘨᘩᘪᘫᘬᘭᘮᘯᘰᘱᘲᘳᘶᘷᘸᘹᘺᘻᘼᘽᘾᘿᙀᙁᙂᙃᙄᙅᙆᙇᙈᙉᙊᙋᙌᙍᙎᙏᙐᙑᙒᙓᙔᙕᙖᙗᙘᙙᙚᙛᙜᙝᙞᙟᙠᙡᙢᙣᙤᙥᙦᙧᙨᙩᙪᙫᙬᙯᙰᙱᙲᙳᙴᙵᙶᙷᙸᙹᙺᙻᙼᙽᙾᙿᢰᢱᢲᢳᢴᢵᢶᢷᢸᢹᢺᢻᢼᢽᢾᢿᣀᣁᣂᣃᣄᣅᣆᣇᣈᣉᣊᣋᣌᣍᣎᣏᣐᣑᣒᣓᣔᣕᣖᣗᣘᣙᣚᣛᣜᣝᣞᣟᣠᣡᣢᣣᣤᣥᣦᣧᣨᣩᣪᣫᣬᣭᣮᣯᣰᣱᣲᣳᣴⶀⶁⶂⶃⶄⶅⶆⶇⶈⶉⶊⶋⶌⶍⶎⶏⶐⶑⶒⶓⶔⶕⶖⶠⶡⶢⶣⶤⶥⶦⶨⶩⶪⶫⶬⶭⶮⶰⶱⶲⶳⶴⶵⶶⶸⶹⶺⶻⶼⶽⶾⷀⷁⷂⷃⷄⷅⷆⷈⷉⷊⷋⷌⷍⷎⷐⷑⷒⷓⷔⷕⷖⷘⷙⷚⷛⷜⷝⷞ"; // Each letter's uppercase, in the same order (uppercase.txt). const UPPERCASE = "0123456789ABCDEFGHIJKNOPQRSTUVWXYZÐØÞĐĦĸŁŦɃƂƇƋƑǶƘȽƛȠƢƤƧƪƫƬƳƵƸƺƻƾǂǃƎǤȡȤȴȵȶȷȸȹȻⱾⱿɁɆɈɊɌɎⱯⱰƁƆƉƊɘɚƐꞫɝɞɟƓɢɤꞍꞪɧƗⱢꞭɭɮɰⱮƝɳɴƟɶɷɸɹɺɻɼⱤɾɿƦʁꟅʄʅʆꞱƮɄƱɅʍʎʐʓʔʕʖʗʘʙʚʛʜꞲꞰʟʠʡʢʤʥʧʨʩʬʭʮʯͰͲͶϾϿΒΔΖΘΛΜΞΠΤΧΨΩϏϘϚϜϞϠϺϼДЖЛЦЧШЩЪЫЭЮЯЂЉЊЏѤѦѨѪѬѮѸѺѼѾҀҊҎҐҔҖҘҚҜҞҠҢҤҦҨҪҬҲҴҶҸҾӃӅӇӍӶӺӼӾԂԄԆԈԊԎԒԔԖԘԞԠԢԤሀሁሂሃሄህሆሇለሉሊላሌልሎሏሐሑሒሓሔሕሖሗመሙሚማሜምሞሟሠሡሢሤሥሦሧረሩሪራሬርሮሯሰሱሲሳሴስሶሷሸሹሺሻሼሽሾሿቀቁቂቃቄቅቆቇቈቊቋቌቍቐቑቒቓቔቕቖቘቚቛቜቝበቡቢባቤብቦቧቨቩቪቫቬቭቮቯተቱቲታቴትቶቷቸቹቺቻቼችቾቿኀኁኂኃኄኅኆኇኈኊኋኌኍነኑኒናኔንኖኗኘኙኚኛኜኝኞኟአኡኢኣኤእኦኧከኩኪካኬክኮኯኰኲኳኴኵኸኹኺኻኼኽኾዀዂዃዄዅወዉዊዋዌውዎዏዑዒዓዔዕዖዘዙዚዛዜዝዞዟዠዡዢዣዤዥዦዧየዩዪያዬይዮዯደዱዲዳዴድዶዷዸዹዺዻዼዽዾዿጀጁጂጃጄጅጆጇገጉጊጋጌግጎጏጐጒጓጔጕጘጙጚጛጜጝጞጟጠጡጢጣጤጥጦጧጨጩጪጫጬጭጮጯጰጱጲጳጴጵጶጷጸጹጺጻጼጽጾጿፀፁፂፃፄፅፆፇፈፉፊፋፌፍፎፏፐፑፒፓፔፕፖፗፘፙፚᎀᎁᎂᎃᎄᎅᎆᎇᎈᎉᎊᎋᎌᎍᎎᎏᐁᐂᐃᐄᐅᐆᐇᐈᐉᐊᐋᐌᐍᐎᐏᐐᐑᐒᐓᐔᐕᐖᐗᐘᐙᐚᐛᐜᐝᐟᐡᐢᐣᐤᐥᐦᐨᐩᐪᐫᐬᐭᐮᐯᐰᐱᐲᐳᐴᐵᐶᐷᐸᐹᐺᐻᐼᐽᐾᐿᑁᑂᑃᑄᑅᑆᑇᑈᑉᑋᑍᑏᑐᑑᑒᑓᑔᑕᑖᑗᑘᑙᑚᑛᑜᑞᑟᑠᑡᑢᑣᑤᑥᑦᑧᑨᑩᑪᑫᑬᑭᑮᑰᑱᑳᑴᑵᑶᑷᑸᑹᑺᑻᑼᑽᑾᑿᒀᒁᒂᒃᒄᒅᒆᒇᒈᒉᒊᒋᒌᒍᒎᒏᒐᒑᒒᒓᒔᒕᒖᒗᒘᒙᒚᒛᒜᒝᒞᒟᒠᒡᒢᒣᒤᒥᒦᒧᒨᒩᒪᒫᒬᒭᒮᒯᒰᒱᒲᒳᒴᒵᒶᒷᒸᒹᒺᒻᒼᒽᒾᓀᓁᓂᓃᓄᓅᓆᓇᓈᓉᓊᓋᓌᓍᓎᓏᓐᓒᓓᓔᓕᓖᓗᓘᓙᓚᓛᓜᓝᓞᓟᓠᓡᓢᓣᓤᓥᓦᓧᓨᓩᓪᓫᓬᓭᓮᓯᓰᓱᓲᓳᓴᓵᓶᓷᓸᓹᓺᓻᓼᓽᓾᓿᔀᔁᔂᔃᔄᔅᔆᔇᔈᔉᔊᔌᔍᔎᔏᔐᔑᔒᔓᔔᔕᔖᔗᔘᔙᔚᔛᔜᔝᔞᔟᔠᔡᔢᔣᔤᔥᔦᔧᔨᔩᔪᔫᔬᔭᔮᔯᔰᔱᔲᔳᔴᔵᔶᔷᔸᔹᔺᔻᔼᔽᔾᔿᕂᕃᕄᕅᕆᕇᕈᕉᕊᕋᕌᕍᕎᕏᕑᕒᕓᕔᕕᕖᕗᕘᕙᕚᕛᕜᕝᕞᕟᕠᕡᕢᕣᕤᕥᕦᕧᕨᕩᕪᕫᕬᕭᕮᕯᕰᕱᕲᕳᕴᕵᕶᕸᕹᕺᕻᕼᕾᕿᖀᖁᖂᖃᖄᖅᖆᖇᖈᖉᖊᖋᖌᖍᖎᖏᖐᖑᖒᖓᖔᖗᖘᖙᖚᖛᖜᖝᖞᖟᖠᖡᖢᖣᖤᖥᖦᖧᖨᖩᖪᖫᖬᖭᖮᖰᖱᖲᖳᖴᖵᖶᖷᖸᖹᖺᖻᖼᖽᖾᖿᗀᗁᗂᗃᗄᗅᗆᗇᗈᗉᗊᗋᗌᗍᗎᗏᗐᗑᗒᗓᗔᗕᗖᗗᗘᗙᗚᗛᗜᗝᗞᗟᗠᗡᗢᗣᗤᗥᗦᗧᗨᗩᗫᗬᗭᗮᗯᗰᗱᗲᗳᗴᗵᗶᗷᗸᗹᗺᗻᗼᗽᗾᗿᘀᘁᘅᘆᘈᘉᘊᘋᘌᘍᘎᘏᘐᘑᘒᘓᘔᘕᘖᘗᘘᘙᘚᘛᘜᘝᘞᘟᘠᘡᘥᘦᘧᘨᘩᘪᘫᘬᘭᘮᘯᘰᘱᘲᘳᘶᘷᘸᘹᘺᘻᘼᘽᘾᘿᙀᙁᙂᙃᙄᙅᙆᙇᙈᙉᙊᙋᙌᙍᙎᙏᙐᙑᙒᙓᙔᙕᙖᙗᙘᙙᙚᙛᙜᙝᙞᙟᙠᙡᙢᙣᙤᙥᙦᙧᙨᙩᙪᙫᙬᙯᙰᙱᙲᙳᙴᙵᙶᙷᙸᙹᙺᙻᙼᙽᙾᙿᢰᢱᢲᢳᢴᢵᢶᢷᢸᢹᢺᢻᢼᢽᢾᢿᣀᣁᣂᣃᣄᣅᣆᣇᣈᣉᣊᣋᣌᣍᣎᣏᣐᣑᣒᣓᣔᣕᣖᣗᣘᣙᣚᣛᣜᣝᣞᣟᣠᣡᣢᣣᣤᣥᣦᣧᣨᣩᣪᣫᣬᣭᣮᣯᣰᣱᣲᣳᣴⶀⶁⶂⶃⶄⶅⶆⶇⶈⶉⶊⶋⶌⶍⶎⶏⶐⶑⶒⶓⶔⶕⶖⶠⶡⶢⶣⶤⶥⶦⶨⶩⶪⶫⶬⶭⶮⶰⶱⶲⶳⶴⶵⶶⶸⶹⶺⶻⶼⶽⶾⷀⷁⷂⷃⷄⷅⷆⷈⷉⷊⷋⷌⷍⷎⷐⷑⷒⷓⷔⷕⷖⷘⷙⷚⷛⷜⷝⷞ"; // The code points decoding ignores, as [first, last] ranges (ignored.txt). const IGNORED = [[9, 13], [32, 32], [133, 133], [160, 160], [173, 173], [847, 847], [1564, 1564], [4447, 4448], [5760, 5760], [6068, 6069], [6155, 6159], [8192, 8207], [8232, 8239], [8287, 8303], [12288, 12288], [12644, 12644], [65024, 65039], [65279, 65279], [65440, 65440], [65520, 65528], [113824, 113827], [119155, 119162], [917504, 921599]]; // Sigil: bytes of a known length written as a sigil, one word of letters and // digits that a double-click selects whole, with no two characters alike, and // a check that confirms a reading or picks the right one of several. // The specification is README.md ("The encoding"); python/sigilcode.py is the // reference implementation, and vectors.json the tests every implementation // passes. The data is written in order, unjumbled; README.md's "Why Sigil // writes the data in order" says why. const CHARS = Array.from(ALPHABET); const N = CHARS.length; if (N !== 1353) throw new Error("the alphabet must have 1,353 characters"); const DIGIT = new Map([ ...Array.from(UPPERCASE).map((c, d) => [c, d]), ...CHARS.map((c, d) => [c, d]), ]); const BN = BigInt(N); /** A sigil that encodes no data of the length asked for: `kind` is * "length" (the wrong number of characters), "character" (one outside the * alphabet), "range" (a value too large for the length), or "check" (the check * fails: a character was misread or changed). */ class DecodeError extends Error { constructor(kind, message) { super(message); this.kind = kind; } } function isPrime(k) { if (k < 2) return false; for (let p = 2; p * p <= k; p++) if (k % p === 0) return false; return true; } /** The smallest prime larger than N: K must exceed N (README.md, "Why the * check catches what it does"). */ let LEAST_K = N + 1; while (!isPrime(LEAST_K)) LEAST_K++; /** [n, K] for data of `length` bytes (at least 1): the sigil's characters, * the fewest whose room R = floor(N^n / 2^(8 * length)) holds a prime larger * than N, and K, the largest prime at most R. */ function layout(length) { if (!(Number.isInteger(length) && length >= 1)) throw new RangeError("Sigil encodes at least one byte"); const bits = BigInt(8 * length); let n = 1; while (BN ** BigInt(n) >> bits < BigInt(LEAST_K)) n++; let k = Number(BN ** BigInt(n) >> bits); while (!isPrime(k)) k--; return [n, k]; } const toBig = (b) => b.reduce((v, byte) => (v << 8n) | BigInt(byte), 0n); /** `data`'s sigil, layout(data.length)[0] characters of the alphabet. * Its protection against look-alikes holds for data that looks random (a * secure hash's output, a key, a random identifier): hash or jumble structured data * before encoding it (README.md, "Data with structure: hash it, or jumble it"). */ function encode(data) { const [n, k] = layout(data.length); let w = toBig(data) * BigInt(k); const out = new Array(n); for (let i = n - 1; i >= 0; i--) { out[i] = CHARS[Number(w % BN)]; w /= BN; } return out.join(""); } /** White space and the default-ignorable characters, which decoding skips. */ function ignored(ch) { const p = ch.codePointAt(0); return IGNORED.some(([a, b]) => a <= p && p <= b); } function decode(sigil, length) { const [n, k] = layout(length); const chars = Array.from(sigil) .map((ch, i) => [ch, i]) .filter(([ch]) => !ignored(ch)); if (chars.length !== n) throw new DecodeError("length", `${chars.length} characters; ${length} bytes take ${n}`); let w = 0n; for (const [ch, i] of chars) { const d = DIGIT.get(ch); if (d === undefined) throw new DecodeError("character", `character ${i + 1} (U+${ch.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}) is not in the alphabet`); w = w * BN + BigInt(d); } const K = BigInt(k); if (w >= K << BigInt(8 * length)) throw new DecodeError("range", `its value is too large for ${length} bytes: a character was misread or changed`); if (w % K !== 0n) throw new DecodeError("check", "the check fails: a character was misread or changed"); let v = w / K; const data = new Uint8Array(length); for (let i = length - 1; i >= 0; i--) { data[i] = Number(v & 0xffn); v >>= 8n; } return data; } /** Every data of `length` bytes that some reading decodes to, for a reader * unsure of some characters (an OCR tool, say): `readings` gives each * character's possible readings, one string a character, the likeliest first. * One result is the answer; more mean the readings leave the sigil ambiguous. */ function decodeReadings(readings, length) { const options = readings.map((r) => Array.from(r)); const found = []; const seen = new Set(); const walk = (i, chosen) => { if (i === options.length) { try { const data = decode(chosen, length); const key = Array.from(data).join(","); if (!seen.has(key)) { seen.add(key); found.push(data); } } catch (e) { if (!(e instanceof DecodeError)) throw e; } return; } for (const ch of options[i]) walk(i + 1, chosen + ch); }; walk(0, ""); return found; } globalThis.SigilCode = Object.freeze({encode, decode, decodeReadings, layout, DecodeError}); })();