# HanBase256 toolkit

Represent hashes as Korean syllables for manual entry and sharing. A SHA-256
hash becomes 32 syllables; decoding recovers the same 32 hash bytes.
Every byte maps to exactly one precomposed Hangul syllable.
The alphabet is identical to this project's
`hancode` `base256` alphabet; ordering is part of the wire format.

## Golden alphabet

The approved set is frozen at revision 13. Every byte assignment and all 256
syllables are fixed by `alphabet-golden.json`, included in this toolkit.
The Python and JavaScript modules embed that exact mapping and work without
loading the JSON file. Both expose `ALPHABET_STATUS = "golden"`,
`ALPHABET_REVISION = 12`, and `ALPHABET_SHA256`.

The fingerprint is SHA-256 of `ALPHABET.encode("utf-8")` in byte order,
without a trailing newline:

```
cb8eb7211dfba72795bff845fc9bf03c7e99e9fe524896e573d388b141201507
```

`alphabet.md` and `alphabet.csv` contain the complete byte mapping.
The 256 syllables are in 가나다 order. Position i represents byte i:
가=00, 갈=01, 감=02, 곰=0D, 히=FF.

## Ordering correction

Revision 13 (hancode 0.4.0) restores strict 가나다 byte order. Earlier in-place
substitutions, including 곰=01, were artifacts and are superseded. This changes
encoded strings and checksums. Re-encode the original bytes with this mapping.
Earlier mappings cannot be reliably auto-detected from unmarked text; the new
codec does not apply their decoding aliases. Retain an old decoder only when
explicitly recovering data that was encoded with that old mapping.

## Read aloud without changing the alphabet

For a pause-separated guide using the same byte mapping:

```python
guide = hanbase256.format_for_speech(checked, checksum=True)
assert hanbase256.decode_with_checksum(guide) == digest
```

```js
const guide = formatForSpeech(checked, { checksum: true });
const verified = await decodeWithChecksum(guide);
```

Import `formatForSpeech` from the JavaScript module with the other functions.
To encode a file into a reading guide:

```sh
python3 hanbase256.py --file example.bin --checksum --speak
```

The guide separates every syllable with `·` and displays the final checksum
as `+색`. Say “체크섬” before the checksum. The updated decoders accept dots.
A `+` separator is accepted only by the checksum-verifying decoder and must
precede exactly one final syllable. Formatting checks syntax and alphabet
membership, not the checksum value.

Pause at each dot. Connected speech can change consonants or link them to
following vowels. Grouping in fours does not prevent these changes.
No syllables or byte assignments are changed for the reading guide.

No third-party dependencies. Python 3.9+; modern browsers or Node.js 20+.
Browser SHA-256 and checksum functions require HTTPS or localhost.

## Python

```python
import hashlib
import hanbase256

digest = hashlib.sha256(b"The quick brown fox jumps over the lazy dog").digest()
plain = hanbase256.encode(digest)
checked = hanbase256.encode_with_checksum(digest)
assert hanbase256.decode(plain) == digest
assert hanbase256.decode_with_checksum(checked) == digest
```

Command line:

```sh
python3 hanbase256.py --text "The quick brown fox jumps over the lazy dog" --sha256
python3 hanbase256.py --file example.bin --checksum -o example.hanbase256
python3 hanbase256.py --decode --file example.hanbase256 --checksum --output-format raw -o restored.bin
```

With no --text or --file argument, input comes from stdin. Stdin newlines are
part of the data when encoding. Use --text for exact text without a newline.

## JavaScript

```js
import { encode, decode, sha256, encodeWithChecksum, decodeWithChecksum } from './hanbase256.mjs';

const digest = await sha256(new TextEncoder().encode('The quick brown fox jumps over the lazy dog'));
const plain = encode(digest);
const checked = await encodeWithChecksum(digest);
const original = decode(plain);
const verified = await decodeWithChecksum(checked);
```

The JavaScript module also exports `ALPHABET`, `ALPHABET_REVISION`, `ALPHABET_STATUS`, `ALPHABET_SHA256`, `DECODE_ALIASES`, `toHex`, `fromHex`, `toBase64`,
and `fromBase64`. Byte inputs are Uint8Array instances.

## Optional checksum format

Plain HanBase256 has no checksum. The codecs offer a checked format that appends
the first byte of SHA-256 of the original bytes, then encodes the entire sequence.
That is one extra syllable. The online converter shows plain encoding first,
then spaced and optional checked rows. Both group the payload in fours; `+` separates the checksum. The codecs return a
contiguous string and accept the grouped display form during checksum decoding.
Call the checksum decoder explicitly to verify and remove the final syllable;
plain decoding does not automatically detect a checksum.

It uses the same 8-bit checksum rule as BIP39 for 256 bits of entropy, but
HanBase256 is a separate format. An 8-bit checksum detects most accidental errors;
it is not cryptographic authentication and does not correct errors.

## Reference example

Input: `The quick brown fox jumps over the lazy dog` (UTF-8, no newline)

SHA-256:
`d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592`

HanBase256:
`키인환정검키시열산추외진전결더물어방민팔색레태소대감침참등최퍼역`

HanBase256 spaced+checksum (6d = 색; + is a display separator):
`키인환정 검키시열 산추외진 전결더물 어방민팔 색레태소 대감침참 등최퍼역 +색`

The codec returns the checked string without the `+` display separator.

## Size

An n-byte input becomes n syllables, or n + 1 with a checksum. Each syllable
occupies 3 UTF-8 bytes. This encoding saves display characters, not storage.
It is reversible encoding, not encryption. Grouping with whitespace is allowed.

License: MIT. See LICENSE.
