diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2022-10-18 07:27:49 -0700 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2022-10-18 07:27:49 -0700 |
commit | f6554ffa53bb3247d184afc3c89b077c19c5cc5c (patch) | |
tree | 051d759b01be8017b9aa16f1193c4639a20d40a4 | |
parent | 0012d8688217585c366aaa0f7389f4c8a1ee0dea (diff) | |
download | jp-hash-f6554ffa53bb3247d184afc3c89b077c19c5cc5c.tar.gz jp-hash-f6554ffa53bb3247d184afc3c89b077c19c5cc5c.tar.bz2 jp-hash-f6554ffa53bb3247d184afc3c89b077c19c5cc5c.zip |
New project: JP-Hash
-rw-r--r-- | Makefile | 10 | ||||
-rw-r--r-- | README.md | 111 | ||||
-rw-r--r-- | jp-hash.c | 164 | ||||
-rw-r--r-- | jp-hash.html | 213 | ||||
-rwxr-xr-x | jp-hash.js | 98 | ||||
-rwxr-xr-x | jp-hash.tl | 68 | ||||
-rwxr-xr-x | test.awk | 17 | ||||
-rwxr-xr-x | test.sh | 12 | ||||
-rw-r--r-- | testvec | 71 |
9 files changed, 764 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fd99a3e --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +CFLAGS ?= -Wall -W -Wextra -g # -O2 +.PHONY: all +all: jp-hash jp-hash.tlo +jp-hash: LDLIBS += -lcrypto +jp-hash: jp-hash.o +jp-hash.o: CFLAGS += -DJP_HASH_IMPL=1 -DJP_HASH_MAIN=1 +jp-hash.o: jp-hash.c +jp-hash.tlo: jp-hash.tl; txr --compile=$^:$@ +.PHONY: clean +clean:; rm -f jp-hash jp-hash.o jp-hash.tlo diff --git a/README.md b/README.md new file mode 100644 index 0000000..655be3c --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +## What is JP-Hash? + +JP-Hash is an algorithm which converts any piece of text or other datum into a +textual digest, which has the following properties: + +* length between 8 and 21 characters. + +* consists mostly of lower-case letters. + +* includes one digit. + +* includes one non-alphanumeric character from the set + `!`, `#`, `@`, `$`, `%`, `^`, `&`, `*`, `?` and `/`. + +By amazing coincidence, these requirements are very similar to +common requirements imposed on people are creating or +changing a password. + +Additionally: + +* The digest string is based on combinations of vowels from the Japanese + language, written in romanized form. This means that many of the digests are + memorable and pronounceable, and have a vibe to them that is pleasing to + enthusiasts for things Japanese. + +## How do I get it? + +See the reference implementation source files. Code is given in +TXR Lisp, C and Javascript for the browser as well as Node.js. + +The self-contained <code>jp-hash.html</code> file should load in any browser, +providing a simple UI. + +## What are the details of the algorithm? + +1. First, the input is hashed via the standard SHA256 sum. + +2. Next, the first 18 bytes of the digest are interpreted as an array of 9 + (nine) 16-bit words, little endian. This array is referred to as `word[0]` + through `word[8]`. + +3. Six pseudo-Japanese syllables are derived from `word[0]` through `word[5]` + as follows: each of these word values is reduced to the remainder modulo 97. + Then, the remainder is used as an index into the following array of 97 + strings. The first letter of the first syllable is then capitalized. + +``` +["a", "i", "u", "e", "o", "ya", "yu", "yo", "wa", + "ka", "ki", "ku", "ke", "ko", "ga", "gi", "gu", "ge", "go", + "sa", "shi", "su", "se", "so", "za", "ji", "zu", "ze", "zo", + "ta", "chi", "tsu", "te", "to", "da", "de", "do", + "na", "ni", "nu", "ne", "no", "ha", "hi", "fu", "he", "ho", + "pa", "pi", "pu", "pe", "po", "ba", "bi", "bu", "be", "bo", + "ma", "mi", "mu", "me", "mo", "ra", "ri", "ru", "re", "ro", + "kya", "kyu", "kyo", "gya", "gyu", "gyo", "sha", "shu", "sho", + "ja", "ju", "jo", "cha", "chu", "cho", "nya", "nyu", "nyo", + "hya", "hyu", "hyo", "pya", "pyu", "pyo", "bya", "byu", "byo", + "mya", "myu", "myo", "rya", "ryu", "ryo"] +``` + +4. A digit is chosen using the modulo 10 remainder of `word[6]` as an index + into the digits `0` through `9`. + +5. Similarly, a symbol is chosen using the modulo 10 remainder of `word[7]` as + an index into the aforementioned list `!`, `#`, `@`, `$`, `%`, `^`, `&`, + `*`, `?` and `/`. + +6. The modulo 8 value of `word[8]` is used to select eight cases (0 to 7) for + combining the above values into an output string. The last four of these + cases insert the `n` (letter n) character into certain places of the string. + The details are in the reference implementation. + +## How many JP-Hash digests are there? + +Since there are six syllables chosen from a set of 92, plus two characters each +from a set of ten, the initial steps yield a space of 83,297,200,492,900 (83.3 +(American) trillion). The 8 cases in step (6) all yield distinct results, and +so multiply the space eight-fold to 666,377,603,943,200 possibilities (666.4 +trillion). + +## Are JP-Hash digests secure for password use? +e +JP-Hash is not advertised as being for a specific purpose. In a security +setting, each user must perform their own analysis to understand the security +risks of using any tool in certain ways and with certain inputs. + +## Examples + +These examples come from the `testvec` file. + +``` +a --> Mina4gai@gashan +y --> Shaba%megyu2shize +Mike --> !Tosuda2bukyochon +Romeo --> Potsun&gaso5machi +Sierra --> Nodon&yanu6zuchi +Tango --> Gyoda#hosa6segi +Whiskey --> Muji?pyuna6gyage +sashimi --> Izu0gyubya/gyumyu +ramen --> Byumi$betsu0nyohe +soba --> Arushin^hyapyuryu2 +futon --> Kyoriton#kyaseku1 +``` + +## License + +The JP-Hash reference code is offered under the a one-clause variant of the BSD +license. See the copyright headers in the source files. + +If you publish altered versions of this algorithm, please don't call it +JP-Hash, thanks! If it doesn't pass the `testvec`, it isn't JP-Hash. diff --git a/jp-hash.c b/jp-hash.c new file mode 100644 index 0000000..d4bb4d5 --- /dev/null +++ b/jp-hash.c @@ -0,0 +1,164 @@ +/* + * One-Clause BSD License ("1BSD") + * + * Copyright 2022 Kaz Kylheku <kaz@kylheku.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is + * met: + * + * 1. The source code distribution retains the above copyright notice, + * this condition, and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JP_HASH_H +#define JP_HASH_H + +#ifdef __cplusplus +extern "C" { +#endif + +void jp_hash(char *out, const char *in); + +#ifdef __cplusplus +} +#endif + +#if JP_HASH_IMPL + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <openssl/sha.h> + +#define elof(array) ((int)(sizeof(array)/sizeof(array[0]))) + +static const char *mora[] = { + "a", "i", "u", "e", "o", "ya", "yu", "yo", "wa", + "ka", "ki", "ku", "ke", "ko", "ga", "gi", "gu", "ge", "go", + "sa", "shi", "su", "se", "so", "za", "ji", "zu", "ze", "zo", + "ta", "chi", "tsu", "te", "to", "da", "de", "do", + "na", "ni", "nu", "ne", "no", "ha", "hi", "fu", "he", "ho", + "pa", "pi", "pu", "pe", "po", "ba", "bi", "bu", "be", "bo", + "ma", "mi", "mu", "me", "mo", "ra", "ri", "ru", "re", "ro", + "kya", "kyu", "kyo", "gya", "gyu", "gyo", "sha", "shu", "sho", + "ja", "ju", "jo", "cha", "chu", "cho", "nya", "nyu", "nyo", + "hya", "hyu", "hyo", "pya", "pyu", "pyo", "bya", "byu", "byo", + "mya", "myu", "myo", "rya", "ryu", "ryo" +}; + +static const char *digit[] = { + "0", "1", "2", "3", "4", + "5", "6", "7", "8", "9" +}; + +static const char *symbol[] = { + "!", "#", "@", "$", "%", + "^", "&", "*", "?", "/" +}; + +void jp_hash(char *out, const char *in) +{ + unsigned char hash[32]; + uint16_t word[9]; + const char *ms[6]; + const char *sym; + const char *dig; + char *ms0; + int i; + + SHA256((const unsigned char *) in, strlen(in), hash); + + for (i = 0; i < elof(word); i++) + word[i] = hash[2*i] | (((uint16_t) hash[2*i + 1]) << 8); + + for (i = 0; i < elof(ms); i++) + ms[i] = mora[word[i] % elof(mora)]; + + dig = digit[word[6] % elof(digit)]; + sym = symbol[word[7] % elof(symbol)]; + + ms0 = strdup(ms[0]); + ms0[0] = toupper((unsigned char) ms0[0]); + ms[0] = ms0; + + switch (word[8] & 7) { + case 0: + snprintf(out, 32, "%s%s%s%s%s%s%s%s", + ms[0], ms[1], ms[2], sym, + ms[3], ms[4], ms[5], dig); + break; + case 1: + snprintf(out, 32, "%s%s%s%s%s%s%s%s", + sym, ms[0], ms[1], ms[2], + dig, ms[3], ms[4], ms[5]); + break; + case 2: + snprintf(out, 32, "%s%s%s%s%s%s%s%s", + ms[0], ms[1], sym, ms[2], + ms[3], dig, ms[4], ms[5]); + break; + case 3: + snprintf(out, 32, "%s%s%s%s%s%s%s%s", + ms[0], ms[1], dig, ms[2], + ms[3], sym, ms[4], ms[5]); + break; + case 4: + snprintf(out, 32, "%s%s%sn%s%s%s%s%s", + ms[0], ms[1], ms[2], sym, + ms[3], ms[4], ms[5], dig); + break; + case 5: + snprintf(out, 32, "%s%s%s%s%s%s%s%sn", + sym, ms[0], ms[1], ms[2], + dig, ms[3], ms[4], ms[5]); + break; + case 6: + snprintf(out, 32, "%s%sn%s%s%s%s%s%s", + ms[0], ms[1], sym, ms[2], + ms[3], dig, ms[4], ms[5]); + break; + case 7: + snprintf(out, 32, "%s%s%s%s%s%s%s%sn", + ms[0], ms[1], dig, ms[2], + ms[3], sym, ms[4], ms[5]); + break; + } + + free(ms0); +} + +#if JP_HASH_MAIN + +int main(int argc, char **argv) +{ + char jph[32]; + + if (argc != 2) { + if (argc) + fprintf(stderr, "%s: one argument required\n", argv[0]); + return EXIT_FAILURE; + } + + jp_hash(jph, argv[1]); + puts(jph); + return 0; +} + +#endif // JP_HASH_MAIN +#endif // JP_HASH_IMPL +#endif // JP_HASH_H diff --git a/jp-hash.html b/jp-hash.html new file mode 100644 index 0000000..2a1514f --- /dev/null +++ b/jp-hash.html @@ -0,0 +1,213 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>JP-Hash</title> + </head> + <body> + <script type="text/javascript"> + +// One-Clause BSD License ("1BSD") +// +// Copyright 2022 Kaz Kylheku <kaz@kylheku.com> +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following condition is +// met: +// +// 1. The source code distribution retains the above copyright notice, +// this condition, and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +const mora = [ + "a", "i", "u", "e", "o", "ya", "yu", "yo", "wa", + "ka", "ki", "ku", "ke", "ko", "ga", "gi", "gu", "ge", "go", + "sa", "shi", "su", "se", "so", "za", "ji", "zu", "ze", "zo", + "ta", "chi", "tsu", "te", "to", "da", "de", "do", + "na", "ni", "nu", "ne", "no", "ha", "hi", "fu", "he", "ho", + "pa", "pi", "pu", "pe", "po", "ba", "bi", "bu", "be", "bo", + "ma", "mi", "mu", "me", "mo", "ra", "ri", "ru", "re", "ro", + "kya", "kyu", "kyo", "gya", "gyu", "gyo", "sha", "shu", "sho", + "ja", "ju", "jo", "cha", "chu", "cho", "nya", "nyu", "nyo", + "hya", "hyu", "hyo", "pya", "pyu", "pyo", "bya", "byu", "byo", + "mya", "myu", "myo", "rya", "ryu", "ryo" +]; + +const digit = [ + "0", "1", "2", "3", "4", + "5", "6", "7", "8", "9" +]; + +const symbol = [ + "!", "#", "@", "$", "%", + "^", "&", "*", "?", "/" +]; + +function jp_hash(input) +{ + const hash = sha256(input); + const view = new DataView(hash); + let word = []; + let ms = []; + + for (let i = 0; i < 9; i++) + word[i] = view.getUint16(2*i, true); + + for (i = 0; i < 6; i++) + ms[i] = mora[word[i] % mora.length]; + + const dig = digit[word[6] % digit.length]; + const sym = symbol[word[7] % symbol.length]; + + ms[0] = ms[0][0].toUpperCase() + ms[0].slice(1); + + switch (word[8] & 7) { + case 0: + return [ms[0], ms[1], ms[2], sym, ms[3], ms[4], ms[5], dig].join(''); + case 1: + return [sym, ms[0], ms[1], ms[2], dig, ms[3], ms[4], ms[5]].join(''); + case 2: + return [ms[0], ms[1], sym, ms[2], ms[3], dig, ms[4], ms[5]].join(''); + case 3: + return [ms[0], ms[1], dig, ms[2], ms[3], sym, ms[4], ms[5]].join(''); + case 4: + return [ms[0], ms[1], ms[2], "n", sym, ms[3], ms[4], ms[5], dig].join(''); + case 5: + return [sym, ms[0], ms[1], ms[2], dig, ms[3], ms[4], ms[5], "n"].join(''); + case 6: + return [ms[0], ms[1], "n", sym, ms[2], ms[3], dig, ms[4], ms[5]].join(''); + case 7: + return [ms[0], ms[1], dig, ms[2], ms[3], sym, ms[4], ms[5], "n"].join(''); + } +} + + +// Copyright 2022 Andrea Griffini +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +function sha256(data) +{ + let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a; + let h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19; + let tsz = 0, bp = 0; + const k = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ]; + const rrot = (x, n) => (x >>> n) | (x << (32-n)); + const w = new Uint32Array(64); + const buf = new Uint8Array(64); + const process = () => { + for (let j=0,r=0; j<16; j++,r+=4) { + w[j] = (buf[r]<<24) | (buf[r+1]<<16) | (buf[r+2]<<8) | buf[r+3]; + } + for (let j=16; j<64; j++) { + let s0 = rrot(w[j-15], 7) ^ rrot(w[j-15], 18) ^ (w[j-15] >>> 3); + let s1 = rrot(w[j-2], 17) ^ rrot(w[j-2], 19) ^ (w[j-2] >>> 10); + w[j] = (w[j-16] + s0 + w[j-7] + s1) | 0; + } + let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7; + for (let j=0; j<64; j++) { + let S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25); + let ch = (e & f) ^ ((~e) & g); + let t1 = (h + S1 + ch + k[j] + w[j]) | 0; + let S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22); + let maj = (a & b) ^ (a & c) ^ (b & c); + let t2 = (S0 + maj) | 0; + h = g; g = f; f = e; e = (d + t1)|0; d = c; c = b; b = a; a = (t1 + t2)|0; + } + h0 = (h0 + a)|0; h1 = (h1 + b)|0; h2 = (h2 + c)|0; h3 = (h3 + d)|0; + h4 = (h4 + e)|0; h5 = (h5 + f)|0; h6 = (h6 + g)|0; h7 = (h7 + h)|0; + bp = 0; + }; + const add = data => { + if (typeof data === "string") { + data = (new TextEncoder).encode(data); + } + for (let i=0; i<data.length; i++) { + buf[bp++] = data[i]; + if (bp === 64) process(); + } + tsz += data.length; + }; + const digest = () => { + buf[bp++] = 0x80; if (bp == 64) process(); + if (bp + 8 > 64) { + while (bp < 64) buf[bp++] = 0x00; + process(); + } + while (bp < 58) buf[bp++] = 0x00; + // Max number of bytes is 35,184,372,088,831 + let L = tsz * 8; + buf[bp++] = (L / 1099511627776.) & 255; + buf[bp++] = (L / 4294967296.) & 255; + buf[bp++] = L >>> 24; + buf[bp++] = (L >>> 16) & 255; + buf[bp++] = (L >>> 8) & 255; + buf[bp++] = L & 255; + process(); + let result = new ArrayBuffer(32); + let byte = new Uint8Array(result); + byte[ 0] = h0 >>> 24; byte[ 1] = (h0 >>> 16) & 255; byte[ 2] = (h0 >>> 8) & 255; byte[ 3] = h0 & 255; + byte[ 4] = h1 >>> 24; byte[ 5] = (h1 >>> 16) & 255; byte[ 6] = (h1 >>> 8) & 255; byte[ 7] = h1 & 255; + byte[ 8] = h2 >>> 24; byte[ 9] = (h2 >>> 16) & 255; byte[10] = (h2 >>> 8) & 255; byte[11] = h2 & 255; + byte[12] = h3 >>> 24; byte[13] = (h3 >>> 16) & 255; byte[14] = (h3 >>> 8) & 255; byte[15] = h3 & 255; + byte[16] = h4 >>> 24; byte[17] = (h4 >>> 16) & 255; byte[18] = (h4 >>> 8) & 255; byte[19] = h4 & 255; + byte[20] = h5 >>> 24; byte[21] = (h5 >>> 16) & 255; byte[22] = (h5 >>> 8) & 255; byte[23] = h5 & 255; + byte[24] = h6 >>> 24; byte[25] = (h6 >>> 16) & 255; byte[26] = (h6 >>> 8) & 255; byte[27] = h6 & 255; + byte[28] = h7 >>> 24; byte[29] = (h7 >>> 16) & 255; byte[30] = (h7 >>> 8) & 255; byte[31] = h7 & 255; + return result; + }; + if (data === undefined) + return {add, digest}; + add(data); + return digest(); +} + +function get_jp_hash() +{ + const input = document.getElementById("input"); + const output = document.getElementById("output"); + output.value = jp_hash(input.value); +} + + </script> + <input type="text" id="input" onkeydown="if (event.key == 'Enter') get_jp_hash();"> + <button type="button" onclick="get_jp_hash()">Get JP-Hash</button> + <input type="text" id="output" readonly> + </body> +</html> diff --git a/jp-hash.js b/jp-hash.js new file mode 100755 index 0000000..12d7673 --- /dev/null +++ b/jp-hash.js @@ -0,0 +1,98 @@ +#!/usr/bin/env node + +// One-Clause BSD License ("1BSD") +// +// Copyright 2022 Kaz Kylheku <kaz@kylheku.com> +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following condition is +// met: +// +// 1. The source code distribution retains the above copyright notice, +// this condition, and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +const mora = [ + "a", "i", "u", "e", "o", "ya", "yu", "yo", "wa", + "ka", "ki", "ku", "ke", "ko", "ga", "gi", "gu", "ge", "go", + "sa", "shi", "su", "se", "so", "za", "ji", "zu", "ze", "zo", + "ta", "chi", "tsu", "te", "to", "da", "de", "do", + "na", "ni", "nu", "ne", "no", "ha", "hi", "fu", "he", "ho", + "pa", "pi", "pu", "pe", "po", "ba", "bi", "bu", "be", "bo", + "ma", "mi", "mu", "me", "mo", "ra", "ri", "ru", "re", "ro", + "kya", "kyu", "kyo", "gya", "gyu", "gyo", "sha", "shu", "sho", + "ja", "ju", "jo", "cha", "chu", "cho", "nya", "nyu", "nyo", + "hya", "hyu", "hyo", "pya", "pyu", "pyo", "bya", "byu", "byo", + "mya", "myu", "myo", "rya", "ryu", "ryo" +]; + +const digit = [ + "0", "1", "2", "3", "4", + "5", "6", "7", "8", "9" +]; + +const symbol = [ + "!", "#", "@", "$", "%", + "^", "&", "*", "?", "/" +]; + +const crypto = require('crypto'); + +function jp_hash(input) +{ + const hash = crypto.createHash('sha256').update(input).digest(); + let word = []; + let ms = []; + + for (let i = 0; i < 9; i++) + word[i] = hash.readUInt16LE(2*i); + + for (i = 0; i < 6; i++) + ms[i] = mora[word[i] % mora.length]; + + const dig = digit[word[6] % digit.length]; + const sym = symbol[word[7] % symbol.length]; + + ms[0] = ms[0][0].toUpperCase() + ms[0].slice(1); + + switch (word[8] & 7) { + case 0: + return [ms[0], ms[1], ms[2], sym, ms[3], ms[4], ms[5], dig].join(''); + case 1: + return [sym, ms[0], ms[1], ms[2], dig, ms[3], ms[4], ms[5]].join(''); + case 2: + return [ms[0], ms[1], sym, ms[2], ms[3], dig, ms[4], ms[5]].join(''); + case 3: + return [ms[0], ms[1], dig, ms[2], ms[3], sym, ms[4], ms[5]].join(''); + case 4: + return [ms[0], ms[1], ms[2], "n", sym, ms[3], ms[4], ms[5], dig].join(''); + case 5: + return [sym, ms[0], ms[1], ms[2], dig, ms[3], ms[4], ms[5], "n"].join(''); + case 6: + return [ms[0], ms[1], "n", sym, ms[2], ms[3], dig, ms[4], ms[5]].join(''); + case 7: + return [ms[0], ms[1], dig, ms[2], ms[3], sym, ms[4], ms[5], "n"].join(''); + } +} + +const myname = process.argv[1]; +const args = process.argv.slice(2); + +if (args.length != 1) { + console.log(myname, ": ", "one argument required"); + process.exit(1); +} + +console.log(jp_hash(args[0])); diff --git a/jp-hash.tl b/jp-hash.tl new file mode 100755 index 0000000..70f70c6 --- /dev/null +++ b/jp-hash.tl @@ -0,0 +1,68 @@ +#!/usr/bin/env txrlisp + +;; One-Clause BSD License ("1BSD") +;; +;; Copyright 2022 Kaz Kylheku <kaz@kylheku.com> +;; +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following condition is +;; met: +;; +;; 1. The source code distribution retains the above copyright notice, +;; this condition, and the following disclaimer. +;; +;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +;; FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +;; COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +;; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +;; BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +;; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +;; LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +;; ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +;; POSSIBILITY OF SUCH DAMAGE. + +(defvarl vowels '#"a i u e o") +(defvarl diphthongs '#"ya yu yo") +(defvarl consonants '#"k g s z t d n h p b m r") +(defvarl symbols '#"! # @ $ % ^ & * ? /") + +(defvarl mora-fix (let ((trie (make-trie))) + (mapdo (op trie-add trie) + '#"si sy zi zy ti tu ty di du dy hu" + '#"shi sh ji j chi tsu ch ji zu j fu") + trie)) + +(defvar mora (flow (append vowels diphthongs '#"wa" + [maprod join consonants vowels] + [maprod join consonants diphthongs]) + (filter-string-tree mora-fix) + uniq)) + +(defun choose (list index) + [list (mod index (len list))]) + +(defun jp-hash (phrase) + (let* ((buf (sha256 phrase)) + (word (carray-buf buf (ffi le-uint16))) + (ms (mapcar (opip word (choose mora)) 0..6)) + (dig (list (pic "#" (mod [word 6] 10)))) + (sym (list (choose symbols [word 7])))) + (upd [ms 0] copy) + (upd [[ms 0] 0] chr-toupper) + (cat-str (caseq (logand [word 8] 7) + (0 (append [ms 0..3] sym [ms 3..6] dig)) + (1 (append sym [ms 0..3] dig [ms 3..6])) + (2 (append [ms 0..2] sym [ms 2..4] dig [ms 4..6])) + (3 (append [ms 0..2] dig [ms 2..4] sym [ms 4..6])) + (4 (append [ms 0..3] '#"n" sym [ms 3..6] dig)) + (5 (append sym [ms 0..3] dig [ms 3..6] '#"n")) + (6 (append [ms 0..2] '#"n" sym [ms 2..4] dig [ms 4..6])) + (7 (append [ms 0..2] dig [ms 2..4] sym [ms 4..6] '#"n")))))) + +(compile-only + (match-case *args* + ((@arg) (put-line (jp-hash arg))) + (@else (put-line `@{self-path}: one argument required`)))) diff --git a/test.awk b/test.awk new file mode 100755 index 0000000..a717ff7 --- /dev/null +++ b/test.awk @@ -0,0 +1,17 @@ +#!/usr/bin/awk -f + +BEGIN { + if (ARGC != 3) { + printf("usage: test.awk <program> <test-vec-file>\n", ARGV[0]); + exit 1 + } + + program = ARGV[1] + file = ARGV[2] + + while ((getline < file) > 0) { + input = ($1 == "EMPTY" ? "" : $1) + program " '" input "'" | getline jhash + print $1, "-->", jhash + } +} @@ -0,0 +1,12 @@ +#!/bin/bash + +success=0 + +printf "testing C implementation\n" +diff -u <(./test.awk ./jp-hash testvec) testvec || success=1 +printf "testing TXR Lisp implementation\n" +diff -u <(./test.awk ./jp-hash.tlo testvec) testvec || success=1 +printf "testing Javascript (Node.js) implementation\n" +diff -u <(./test.awk ./jp-hash.js testvec) testvec || success=1 + +exit $success @@ -0,0 +1,71 @@ +EMPTY --> Nyubyu9rupi#kifun +a --> Mina4gai@gashan +b --> Seru0byuto/sode +c --> Hoke7pujo$gecho +d --> !Bouyo7myapiho +e --> $Nushori7yamiu +f --> Kyogu^pepa4yugyo +g --> Rege!shaga8mehi +h --> Dabyu%myoa9zachu +i --> Seku3yuyu&shojan +j --> Zoteku$tanogyo4 +k --> Dabya3kabo!yain +l --> Ohyu7daro@bezen +m --> Hyunin$tobo3ugyo +n --> Murekya@chonupyo6 +o --> Hebagya*kebubyo1 +p --> Gurepo#jujumo6 +q --> Kishu?deke3eke +r --> Sunoshu*niyana0 +s --> Basha*piryu3kiko +t --> Hyope1sago?yoran +u --> %Dekute4jupeo +v --> Rudo&mese5ibi +w --> &Guike7rirapun +x --> Shagoku!nyopyupya3 +y --> Shaba%megyu2shize +z --> Mayamyo*mabyomu0 +Alfa --> Sabapu/nyonyufu7 +Bravo --> Pyurya1honyu&namyan +Charlie --> !Dapugo1myushanyo +Delta --> Mepyu#sagyu0pyuro +Echo --> Ryonyon?zosu7gimu +Foxtrot --> !Boyoza2pojishi +Golf --> Tepa*chagi0zapo +Hotel --> @Mezocho0bahepi +India --> Shosu$mokyo3myasha +Juliett --> ^Momyugi3nebyuchi +Kilo --> Hyohi4kyoge%boyun +Lima --> Seke*hepi8ryuchu +Mike --> !Tosuda2bukyochon +November --> Najo%gyai8buji +Oscar --> Gamege$byukyuhyo3 +Papa --> Manyan/suya6bichi +Quebec --> Ryukase#toryopo2 +Romeo --> Potsun&gaso5machi +Sierra --> Nodon&yanu6zuchi +Tango --> Gyoda#hosa6segi +Uniform --> Kibyo!kai4pyukyo +Victor --> Wakiyun?jachuro0 +Whiskey --> Muji?pyuna6gyage +X-ray --> Adakon?kunoa9 +Yankee --> Bio&myubyo7pyuna +Zulu --> Pyarunyun&nyoryoshi9 +sushi --> Sachoken$toryojo0 +sashimi --> Izu0gyubya/gyumyu +wasabi --> Kuja0byuu%nobun +onigiri --> Henejun!bemyazo3 +miso --> Ichin^myuha4chojo +ramen --> Byumi$betsu0nyohe +udon --> Samu6fucho!byohon +soba --> Arushin^hyapyuryu2 +donburi --> &Kepyape7desudo +uni --> Shaton&hopa1kyopo +unagi --> Asha6zuhi$ryugyan +sake --> #Mikiyo5musepa +saba --> Masezan%shuguryu9 +futon --> Kyoriton#kyaseku1 +kaizen --> Zahi*soja0zucha +kamikaze --> $Gyamunyu8pusayun +karaoke --> Nopyade!yodakyu5 +pokemon --> !Yumoshu4myohinon |