seedhammer-v1-companion/bc/ur/ur.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

89 lines
2.0 KiB
Go

// Package ur implements the Uniform Resources (UR) encoding
// specified in [BCR-2020-005].
//
// [BCR-2020-005]: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md
package ur
import (
"errors"
"fmt"
"strings"
"github.com/mineracks/seedhammer-v1-companion/bc/bytewords"
"github.com/mineracks/seedhammer-v1-companion/bc/fountain"
)
func Encode(_type string, message []byte, seqNum, seqLen int) string {
if seqLen == 1 {
return fmt.Sprintf("ur:%s/%s", _type, bytewords.Encode(message))
}
data := fountain.Encode(message, seqNum, seqLen)
return fmt.Sprintf("ur:%s/%d-%d/%s", _type, seqNum, seqLen, bytewords.Encode(data))
}
type Decoder struct {
typ string
data []byte
fountain fountain.Decoder
}
func (d *Decoder) Progress() float32 {
if d.data != nil {
return 1
}
return d.fountain.Progress()
}
func (d *Decoder) Result() (string, []byte, error) {
if d.data != nil {
return d.typ, d.data, nil
}
v, err := d.fountain.Result()
if v == nil {
return "", nil, err
}
return d.typ, v, err
}
func (d *Decoder) Add(ur string) error {
ur = strings.ToLower(ur)
const prefix = "ur:"
if !strings.HasPrefix(ur, prefix) {
return errors.New("ur: missing ur: prefix")
}
ur = ur[len(prefix):]
parts := strings.SplitN(ur, "/", 3)
if len(parts) < 2 {
return errors.New("ur: incomplete UR")
}
typ := parts[0]
if d.typ != "" && d.typ != typ {
return errors.New("ur: incompatible fragment")
}
d.typ = typ
var seqAndLen string
var fragment string
if len(parts) == 2 {
fragment = parts[1]
} else {
seqAndLen, fragment = parts[1], parts[2]
}
enc, err := bytewords.Decode(fragment)
if err != nil {
return fmt.Errorf("ur: invalid fragment: %w", err)
}
if seqAndLen != "" {
var seq, n int
if _, err := fmt.Sscanf(seqAndLen, "%d-%d", &seq, &n); err != nil {
return fmt.Errorf("ur: invalid sequence %q", seqAndLen)
}
if err := d.fountain.Add(enc); err != nil {
return err
}
} else {
d.data = enc
}
return nil
}