mirror of
https://github.com/schollz/croc.git
synced 2024-07-10 07:33:12 +02:00
558 lines
13 KiB
Go
558 lines
13 KiB
Go
// Package mnemonicode …
|
|
package mnemonicode
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"golang.org/x/text/transform"
|
|
)
|
|
|
|
// WordsRequired returns the number of words required to encode input
|
|
// data of length bytes using mnomonic encoding.
|
|
//
|
|
// Every four bytes of input is encoded into three words. If there
|
|
// is an extra one or two bytes they get an extra one or two words
|
|
// respectively. If there is an extra three bytes, they will be encoded
|
|
// into three words with the last word being one of a small set of very
|
|
// short words (only needed to encode the last 3 bits).
|
|
func WordsRequired(length int) int {
|
|
return ((length + 1) * 3) / 4
|
|
}
|
|
|
|
// A Config structure contains options for mneomonic encoding.
|
|
//
|
|
// {PREFIX}word{wsep}word{gsep}word{wsep}word{SUFFIX}
|
|
type Config struct {
|
|
LinePrefix string
|
|
LineSuffix string
|
|
WordSeparator string
|
|
GroupSeparator string
|
|
WordsPerGroup uint
|
|
GroupsPerLine uint
|
|
WordPadding rune
|
|
}
|
|
|
|
var defaultConfig = Config{
|
|
LinePrefix: "",
|
|
LineSuffix: "\n",
|
|
WordSeparator: " ",
|
|
GroupSeparator: " - ",
|
|
WordsPerGroup: 3,
|
|
GroupsPerLine: 3,
|
|
WordPadding: ' ',
|
|
}
|
|
|
|
// NewDefaultConfig returns a newly allocated Config initialised with default values.
|
|
func NewDefaultConfig() *Config {
|
|
r := new(Config)
|
|
*r = defaultConfig
|
|
return r
|
|
}
|
|
|
|
// NewEncodeReader returns a new io.Reader that will return a
|
|
// formatted list of mnemonic words representing the bytes in r.
|
|
//
|
|
// The configuration of the word formatting is controlled
|
|
// by c, which can be nil for default formatting.
|
|
func NewEncodeReader(r io.Reader, c *Config) io.Reader {
|
|
t := NewEncodeTransformer(c)
|
|
return transform.NewReader(r, t)
|
|
}
|
|
|
|
// NewEncoder returns a new io.WriteCloser that will write a formatted
|
|
// list of mnemonic words representing the bytes written to w. The user
|
|
// needs to call Close to flush unwritten bytes that may be buffered.
|
|
//
|
|
// The configuration of the word formatting is controlled
|
|
// by c, which can be nil for default formatting.
|
|
func NewEncoder(w io.Writer, c *Config) io.WriteCloser {
|
|
t := NewEncodeTransformer(c)
|
|
return transform.NewWriter(w, t)
|
|
}
|
|
|
|
// NewEncodeTransformer returns a new transformer
|
|
// that encodes bytes into mnemonic words.
|
|
//
|
|
// The configuration of the word formatting is controlled
|
|
// by c, which can be nil for default formatting.
|
|
func NewEncodeTransformer(c *Config) transform.Transformer {
|
|
if c == nil {
|
|
c = &defaultConfig
|
|
}
|
|
return &enctrans{
|
|
c: *c,
|
|
state: needPrefix,
|
|
}
|
|
}
|
|
|
|
type enctrans struct {
|
|
c Config
|
|
state encTransState
|
|
wordCnt uint
|
|
groupCnt uint
|
|
wordidx [3]int
|
|
wordidxcnt int // remaining indexes in wordidx; wordidx[3-wordidxcnt:]
|
|
}
|
|
|
|
func (t *enctrans) Reset() {
|
|
t.state = needPrefix
|
|
t.wordCnt = 0
|
|
t.groupCnt = 0
|
|
t.wordidxcnt = 0
|
|
}
|
|
|
|
type encTransState uint8
|
|
|
|
const (
|
|
needNothing = iota
|
|
needPrefix
|
|
needWordSep
|
|
needGroupSep
|
|
needSuffix
|
|
)
|
|
|
|
func (t *enctrans) strState() (str string, nextState encTransState) {
|
|
switch t.state {
|
|
case needPrefix:
|
|
str = t.c.LinePrefix
|
|
case needWordSep:
|
|
str = t.c.WordSeparator
|
|
case needGroupSep:
|
|
str = t.c.GroupSeparator
|
|
case needSuffix:
|
|
str = t.c.LineSuffix
|
|
nextState = needPrefix
|
|
}
|
|
return
|
|
}
|
|
|
|
func (t *enctrans) advState() {
|
|
t.wordCnt++
|
|
if t.wordCnt < t.c.WordsPerGroup {
|
|
t.state = needWordSep
|
|
} else {
|
|
t.wordCnt = 0
|
|
t.groupCnt++
|
|
if t.groupCnt < t.c.GroupsPerLine {
|
|
t.state = needGroupSep
|
|
} else {
|
|
t.groupCnt = 0
|
|
t.state = needSuffix
|
|
}
|
|
}
|
|
}
|
|
|
|
// transformWords consumes words from wordidx copying the words with
|
|
// formatting into dst.
|
|
// On return, if err==nil, all words were consumed (wordidxcnt==0).
|
|
func (t *enctrans) transformWords(dst []byte) (nDst int, err error) {
|
|
//log.Println("transformWords: len(dst)=",len(dst),"wordidxcnt=",t.wordidxcnt)
|
|
for t.wordidxcnt > 0 {
|
|
for t.state != needNothing {
|
|
str, nextState := t.strState()
|
|
if len(dst) < len(str) {
|
|
return nDst, transform.ErrShortDst
|
|
}
|
|
n := copy(dst, str)
|
|
dst = dst[n:]
|
|
nDst += n
|
|
t.state = nextState
|
|
}
|
|
word := wordList[t.wordidx[3-t.wordidxcnt]]
|
|
n := len(word)
|
|
if n < longestWord {
|
|
if rlen := utf8.RuneLen(t.c.WordPadding); rlen > 0 {
|
|
n += (longestWord - n) * rlen
|
|
}
|
|
}
|
|
if len(dst) < n {
|
|
return nDst, transform.ErrShortDst
|
|
}
|
|
n = copy(dst, word)
|
|
t.wordidxcnt--
|
|
dst = dst[n:]
|
|
nDst += n
|
|
if t.c.WordPadding != 0 {
|
|
for i := n; i < longestWord; i++ {
|
|
n = utf8.EncodeRune(dst, t.c.WordPadding)
|
|
dst = dst[n:]
|
|
nDst += n
|
|
}
|
|
}
|
|
t.advState()
|
|
}
|
|
return nDst, nil
|
|
}
|
|
|
|
// Transform implements the transform.Transformer interface.
|
|
func (t *enctrans) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
//log.Printf("Transform(%d,%d,%t)\n", len(dst), len(src), atEOF)
|
|
var n int
|
|
for {
|
|
if t.wordidxcnt > 0 {
|
|
n, err = t.transformWords(dst)
|
|
dst = dst[n:]
|
|
nDst += n
|
|
if err != nil {
|
|
//log.Printf("\t\t\tRet1: (%d) %d, %d, %v\n", t.wordidxcnt, nDst, nSrc, err)
|
|
return
|
|
}
|
|
}
|
|
var x uint32
|
|
switch {
|
|
case len(src) >= 4:
|
|
x = uint32(src[0])
|
|
x |= uint32(src[1]) << 8
|
|
x |= uint32(src[2]) << 16
|
|
x |= uint32(src[3]) << 24
|
|
src = src[4:]
|
|
nSrc += 4
|
|
|
|
t.wordidx[0] = int(x % base)
|
|
t.wordidx[1] = int(x/base) % base
|
|
t.wordidx[2] = int(x/base/base) % base
|
|
t.wordidxcnt = 3
|
|
//log.Printf("\t\tConsumed 4 bytes (%d, %d)", nDst, nSrc)
|
|
//continue
|
|
case len(src) == 0:
|
|
//log.Printf("\t\t\tRet2: (%d) %d, %d, %v\n", t.wordidxcnt, nDst, nSrc, err)
|
|
return
|
|
case !atEOF:
|
|
//log.Printf("\t\t!atEOF (%d, %d)", nDst, nSrc)
|
|
err = transform.ErrShortSrc
|
|
return
|
|
default:
|
|
x = 0
|
|
n = len(src)
|
|
for i := n - 1; i >= 0; i-- {
|
|
x <<= 8
|
|
x |= uint32(src[i])
|
|
}
|
|
t.wordidx[3-n] = int(x % base)
|
|
if n >= 2 {
|
|
t.wordidx[4-n] = int(x/base) % base
|
|
}
|
|
if n == 3 {
|
|
t.wordidx[2] = base + int(x/base/base)%7
|
|
}
|
|
src = src[n:]
|
|
nSrc += n
|
|
t.wordidxcnt = n
|
|
//log.Printf("\t\tatEOF (%d) (%d, %d)", t.wordidxcnt, nDst, nSrc)
|
|
//continue
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
// NewDecoder returns a new io.Reader that will return the
|
|
// decoded bytes from mnemonic words in r. Unrecognized
|
|
// words in r will cause reads to return an error.
|
|
func NewDecoder(r io.Reader) io.Reader {
|
|
t := NewDecodeTransformer()
|
|
return transform.NewReader(r, t)
|
|
}
|
|
|
|
// NewDecodeWriter returns a new io.WriteCloser that will
|
|
// write decoded bytes from mnemonic words written to it.
|
|
// Unrecognized words will cause a write error. The user needs
|
|
// to call Close to flush unwritten bytes that may be buffered.
|
|
func NewDecodeWriter(w io.Writer) io.WriteCloser {
|
|
t := NewDecodeTransformer()
|
|
return transform.NewWriter(w, t)
|
|
}
|
|
|
|
// NewDecodeTransformer returns a new transform
|
|
// that decodes mnemonic words into the represented
|
|
// bytes. Unrecognized words will trigger an error.
|
|
func NewDecodeTransformer() transform.Transformer {
|
|
return &dectrans{wordidx: make([]int, 0, 3)}
|
|
}
|
|
|
|
type dectrans struct {
|
|
wordidx []int
|
|
short bool // last word in wordidx is/was short
|
|
}
|
|
|
|
func (t *dectrans) Reset() {
|
|
t.wordidx = nil
|
|
t.short = false
|
|
}
|
|
|
|
func (t *dectrans) transformWords(dst []byte) (int, error) {
|
|
//log.Println("transformWords: len(dst)=",len(dst),"len(t.wordidx)=", len(t.wordidx))
|
|
n := len(t.wordidx)
|
|
if n == 3 && !t.short {
|
|
n = 4
|
|
}
|
|
if len(dst) < n {
|
|
return 0, transform.ErrShortDst
|
|
}
|
|
for len(t.wordidx) < 3 {
|
|
t.wordidx = append(t.wordidx, 0)
|
|
}
|
|
x := uint32(t.wordidx[2])
|
|
x *= base
|
|
x += uint32(t.wordidx[1])
|
|
x *= base
|
|
x += uint32(t.wordidx[0])
|
|
for i := 0; i < n; i++ {
|
|
dst[i] = byte(x)
|
|
x >>= 8
|
|
}
|
|
t.wordidx = t.wordidx[:0]
|
|
return n, nil
|
|
}
|
|
|
|
type WordError interface {
|
|
error
|
|
Word() string
|
|
}
|
|
|
|
type UnexpectedWordError string
|
|
type UnexpectedEndWordError string
|
|
type UnknownWordError string
|
|
|
|
func (e UnexpectedWordError) Word() string { return string(e) }
|
|
func (e UnexpectedEndWordError) Word() string { return string(e) }
|
|
func (e UnknownWordError) Word() string { return string(e) }
|
|
func (e UnexpectedWordError) Error() string {
|
|
return fmt.Sprintf("mnemonicode: unexpected word after short word: %q", string(e))
|
|
}
|
|
func (e UnexpectedEndWordError) Error() string {
|
|
return fmt.Sprintf("mnemonicode: unexpected end word: %q", string(e))
|
|
}
|
|
func (e UnknownWordError) Error() string {
|
|
return fmt.Sprintf("mnemonicode: unknown word: %q", string(e))
|
|
}
|
|
|
|
// Transform implements the transform.Transformer interface.
|
|
func (t *dectrans) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
|
//log.Printf("Transform(%d,%d,%t)\n", len(dst), len(src), atEOF)
|
|
var n int
|
|
for len(t.wordidx) > 0 || len(src) > 0 {
|
|
for len(t.wordidx) < 3 {
|
|
var word []byte
|
|
var idx int
|
|
//n, word, err = bufio.ScanWords(src, atEOF)
|
|
n, word, err = scanWords(src, atEOF)
|
|
src = src[n:]
|
|
nSrc += n
|
|
if err != nil {
|
|
//log.Print("ScanWords error:", err)
|
|
return
|
|
}
|
|
if word == nil {
|
|
if atEOF {
|
|
//log.Printf("atEOF (%d, %d) %d, %d", nDst, nSrc, n, len(src))
|
|
n = len(src)
|
|
src = src[n:]
|
|
nSrc += n
|
|
break
|
|
}
|
|
//log.Printf("\t\t!atEOF (%d, %d)", nDst, nSrc)
|
|
err = transform.ErrShortSrc
|
|
return
|
|
}
|
|
if t.short {
|
|
err = UnexpectedWordError(word)
|
|
//log.Print("short error:", err)
|
|
return
|
|
}
|
|
idx, _, t.short, err = closestWordIdx(string(word), len(t.wordidx) == 2)
|
|
if err != nil {
|
|
//log.Print("closestWordIdx error:", err)
|
|
return
|
|
}
|
|
t.wordidx = append(t.wordidx, idx)
|
|
}
|
|
if len(t.wordidx) > 0 {
|
|
n, err = t.transformWords(dst)
|
|
dst = dst[n:]
|
|
nDst += n
|
|
if n != 4 {
|
|
//log.Println("transformWords returned:", n, err)
|
|
//log.Println("len(t.wordidx):", len(t.wordidx), len(src))
|
|
}
|
|
if err != nil {
|
|
//log.Printf("\t\t\tRet1: (%d) %d, %d, %v\n", len(t.wordidx), nDst, nSrc, err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
//
|
|
|
|
const base = 1626
|
|
|
|
// EncodeWordList encodes src into mnemomic words which are appended to dst.
|
|
// The final wordlist is returned.
|
|
// There will be WordsRequired(len(src)) words appeneded.
|
|
func EncodeWordList(dst []string, src []byte) (result []string) {
|
|
if n := len(dst) + WordsRequired(len(src)); cap(dst) < n {
|
|
result = make([]string, len(dst), n)
|
|
copy(result, dst)
|
|
} else {
|
|
result = dst
|
|
}
|
|
|
|
var x uint32
|
|
for len(src) >= 4 {
|
|
x = uint32(src[0])
|
|
x |= uint32(src[1]) << 8
|
|
x |= uint32(src[2]) << 16
|
|
x |= uint32(src[3]) << 24
|
|
src = src[4:]
|
|
|
|
i0 := int(x % base)
|
|
i1 := int(x/base) % base
|
|
i2 := int(x/base/base) % base
|
|
result = append(result, wordList[i0], wordList[i1], wordList[i2])
|
|
}
|
|
if len(src) > 0 {
|
|
x = 0
|
|
for i := len(src) - 1; i >= 0; i-- {
|
|
x <<= 8
|
|
x |= uint32(src[i])
|
|
}
|
|
i := int(x % base)
|
|
result = append(result, wordList[i])
|
|
if len(src) >= 2 {
|
|
i = int(x/base) % base
|
|
result = append(result, wordList[i])
|
|
}
|
|
if len(src) == 3 {
|
|
i = base + int(x/base/base)%7
|
|
result = append(result, wordList[i])
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func closestWordIdx(word string, shortok bool) (idx int, exact, short bool, err error) {
|
|
word = strings.ToLower(word)
|
|
if idx, exact = wordMap[word]; !exact {
|
|
// TODO(dchapes): normalize unicode, remove accents, etc
|
|
// TODO(dchapes): phonetic algorithm or other closest match
|
|
err = UnknownWordError(word)
|
|
return
|
|
}
|
|
if short = (idx >= base); short {
|
|
idx -= base
|
|
if !shortok {
|
|
err = UnexpectedEndWordError(word)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// DecodeWordList decodes the mnemonic words in src into bytes which are
|
|
// appended to dst.
|
|
func DecodeWordList(dst []byte, src []string) (result []byte, err error) {
|
|
if n := (len(src)+2)/3*4 + len(dst); cap(dst) < n {
|
|
result = make([]byte, len(dst), n)
|
|
copy(result, dst)
|
|
} else {
|
|
result = dst
|
|
}
|
|
|
|
var idx [3]int
|
|
for len(src) > 3 {
|
|
if idx[0], _, _, err = closestWordIdx(src[0], false); err != nil {
|
|
return nil, err
|
|
}
|
|
if idx[1], _, _, err = closestWordIdx(src[1], false); err != nil {
|
|
return nil, err
|
|
}
|
|
if idx[2], _, _, err = closestWordIdx(src[2], false); err != nil {
|
|
return nil, err
|
|
}
|
|
src = src[3:]
|
|
x := uint32(idx[2])
|
|
x *= base
|
|
x += uint32(idx[1])
|
|
x *= base
|
|
x += uint32(idx[0])
|
|
result = append(result, byte(x), byte(x>>8), byte(x>>16), byte(x>>24))
|
|
}
|
|
|
|
if len(src) > 0 {
|
|
var short bool
|
|
idx[1] = 0
|
|
idx[2] = 0
|
|
n := len(src)
|
|
for i := 0; i < n; i++ {
|
|
idx[i], _, short, err = closestWordIdx(src[i], i == 2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
x := uint32(idx[2])
|
|
x *= base
|
|
x += uint32(idx[1])
|
|
x *= base
|
|
x += uint32(idx[0])
|
|
result = append(result, byte(x))
|
|
if n > 1 {
|
|
result = append(result, byte(x>>8))
|
|
}
|
|
if n > 2 {
|
|
result = append(result, byte(x>>16))
|
|
if !short {
|
|
result = append(result, byte(x>>24))
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
for len(src) > 0 {
|
|
short := false
|
|
n := len(src)
|
|
if n > 3 {
|
|
n = 3
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
idx[i], _, err = closestWordIdx(src[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if idx[i] >= base {
|
|
if i != 2 || len(src) != 3 {
|
|
return nil, UnexpectedEndWord(src[i])
|
|
}
|
|
short = true
|
|
idx[i] -= base
|
|
}
|
|
}
|
|
for i := n; i < 3; i++ {
|
|
idx[i] = 0
|
|
}
|
|
src = src[n:]
|
|
x := uint32(idx[2])
|
|
x *= base
|
|
x += uint32(idx[1])
|
|
x *= base
|
|
x += uint32(idx[0])
|
|
result = append(result, byte(x))
|
|
if n > 1 {
|
|
result = append(result, byte(x>>8))
|
|
}
|
|
if n > 2 {
|
|
result = append(result, byte(x>>16))
|
|
if !short {
|
|
result = append(result, byte(x>>24))
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
return result, nil
|
|
}
|