seedhammer-v1-companion/bc/bytewords/bytewords.go
mineracks 9261cf368a Lift composer substrate from upstream v1.3.0
Twelve packages lifted from seedhammer/seedhammer @ v1.3.0
(commit 2f071c1d8f23eb7fd39b15fc0acb8874113f801e):

  address/     — bitcoin address parsing
  backup/      — v1 plate dimensions + UR-coded multi-plate backup
  bc/          — Blockchain Commons: ur, fountain, bytewords, urtypes,
                 xoshiro256 (5 subpkgs)
  bip32/       — HD-key derivation
  bip39/       — mnemonic seed phrases + 2048-word wordlist
  engrave/     — text/QR → MoveTo/LineTo command stream conversion
  seedqr/      — SeedQR / CompactSeedQR encoders
  image/       — paletted, rgb565, alpha4, ninepatch image formats
  nonstandard/ — bitcoin descriptor + script parsing
  font/        — bitmap + vector font runtime
  font/{comfortaa,poppins,constant,bitmap,vector}/  — actual fonts
  driver/mjolnir/  — MarkingWay USB-serial engraver driver

Plus an earlier-aside backup_test.go restored (its deps are now lifted).

Import paths globally rewritten seedhammer.com → mineracks namespace
via single sed pass; verified no orphan refs remain. go.mod adopts
upstream's full dep set plus the replace-directive for the patched
kortschak/qr fork.

  go build ./...     clean (all 27 packages)
  go test ./...      clean (12 packages with tests, all passing)

NOT lifted in this commit:
  - driver/{wshat,drm,libcamera}  (hardware-specific GPIO/LCD/camera —
    will be platform-v1/-shaped abstractions instead)
  - gui/                (depends on the above; lifts in Phase 2)
  - cmd/{controller,...} (Pi binary entrypoints — not needed for the
    companion repo)
  - zbar/               (QR scanner — needs libcamera)

Next:
  - Write the SH1E reference encoder/decoder in engrave/wire/sh1e/
  - Lift Gangleri42's cmd/webnfc/ shell + retune to v1 plates
  - First buildable composer WASM with a working preview

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 18:36:40 +10:00

121 lines
3.9 KiB
Go

// Package bytewords implements the the bytewords standard
// as described in [BCR-2020-012].
//
// [BCR-2020-012]: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md
package bytewords
import (
"encoding/binary"
"errors"
"hash/crc32"
)
type byteview interface {
~string | ~[]byte
}
func Encode(data []byte) string {
buf := make([]byte, 0, (len(data)+4)*2)
for _, b := range data {
i := int(b) * 2
buf = append(buf, abbrev[i:i+2]...)
}
check := crc32.ChecksumIEEE(data)
var checkb [4]byte
binary.BigEndian.PutUint32(checkb[:], check)
for _, b := range checkb {
i := int(b) * 2
buf = append(buf, abbrev[i:i+2]...)
}
return string(buf)
}
func Decode[T byteview](src T) ([]byte, error) {
if len(src)%2 == 1 {
return nil, errors.New("truncated input")
}
dst := make([]byte, len(src)/2)
if len(dst) < 4 {
return nil, errors.New("input too short")
}
for i := range dst {
w, ok := lookup(src[i*2], src[i*2+1])
if !ok {
return nil, errors.New("invalid word")
}
dst[i] = w
}
res := dst[:len(dst)-4]
got := binary.BigEndian.Uint32(dst[len(dst)-4:])
want := crc32.ChecksumIEEE(res)
if got != want {
return nil, errors.New("crc32 checksum mismatch")
}
return res, nil
}
func lookup(l1, l2 byte) (byte, bool) {
idx := l1 - 'a'
if int(idx) >= len(firstLetters) {
return 0, false
}
start := firstLetters[l1-'a']
for i := int(start); i < len(abbrev)/2; i++ {
w0, w3 := abbrev[i*2], abbrev[i*2+1]
if w0 != l1 {
break
}
if w3 == l2 {
return byte(i), true
}
}
return 0, false
}
var firstLetters [26]uint8
func init() {
var letter byte = 'a' - 1
for i := 0; i < len(abbrev)/2; i++ {
if l1 := abbrev[i*2]; l1 != letter {
letter = l1
firstLetters[letter-'a'] = uint8(i)
}
}
}
// abbrev contains the two-letter abbreviations for the bytewords word list:
// able, acid, also, apex, aqua, arch, atom, aunt,
// away, axis, back, bald, barn, belt, beta, bias,
// blue, body, brag, brew, bulb, buzz, calm, cash,
// cats, chef, city, claw, code, cola, cook, cost,
// crux, curl, cusp, cyan, dark, data, days, deli,
// dice, diet, door, down, draw, drop, drum, dull,
// duty, each, easy, echo, edge, epic, even, exam,
// exit, eyes, fact, fair, fern, figs, film, fish,
// fizz, flap, flew, flux, foxy, free, frog, fuel,
// fund, gala, game, gear, gems, gift, girl, glow,
// good, gray, grim, guru, gush, gyro, half, hang,
// hard, hawk, heat, help, high, hill, holy, hope,
// horn, huts, iced, idea, idle, inch, inky, into,
// iris, iron, item, jade, jazz, join, jolt, jowl,
// judo, jugs, jump, junk, jury, keep, keno, kept,
// keys, kick, kiln, king, kite, kiwi, knob, lamb,
// lava, lazy, leaf, legs, liar, limp, lion, list,
// logo, loud, love, luau, luck, lung, main, many,
// math, maze, memo, menu, meow, mild, mint, miss,
// monk, nail, navy, need, news, next, noon, note,
// numb, obey, oboe, omit, onyx, open, oval, owls,
// paid, part, peck, play, plus, poem, pool, pose,
// puff, puma, purr, quad, quiz, race, ramp, real,
// redo, rich, road, rock, roof, ruby, ruin, runs,
// rust, safe, saga, scar, sets, silk, skew, slot,
// soap, solo, song, stub, surf, swan, taco, task,
// taxi, tent, tied, time, tiny, toil, tomb, toys,
// trip, tuna, twin, ugly, undo, unit, urge, user,
// vast, very, veto, vial, vibe, view, visa, void,
// vows, wall, wand, warm, wasp, wave, waxy, webs,
// what, when, whiz, wolf, work, yank, yawn, yell,
// yoga, yurt, zaps, zero, zest, zinc, zone, zoom.
const abbrev = "aeadaoaxaaahamatayasbkbdbnbtbabsbebybgbwbbbzcmchcscfcycwcecackctcxclcpcndkdadsdidedtdrdndwdpdmdldyeheyeoeeecenemetesftfrfnfsfmfhfzfpfwfxfyfefgflfdgagegrgsgtglgwgdgygmgughgohfhghdhkhthphhhlhyhehnhsidiaieihiyioisinimjejzjnjtjljojsjpjkjykpkoktkskkknkgkekikblblalylflslrlplnltloldlelulklgmnmymhmemomumwmdmtmsmknlnyndnsntnnnenboyoeotoxonolospdptpkpypspmplpepfpaprqdqzrerprlrorhrdrkrfryrnrsrtsesasrssskswstspsosgsbsfsntotktitttdtetytltbtstptatnuyuoutueurvtvyvovlvevwvavdvswlwdwmwpwewywswtwnwzwfwkykynylyaytzszoztzczezm"