For whoever does not trust the random numbers of a computer, as the author decided: five dice for each word, read in a fixed order, give a number from 11111 to 66666, the position of the word in a list of 7776 words, the first die the most significant. wordkey.DiceNumber and DiceWord number the words and give the word of a number; DiceWords takes several numbers, at least 6 and never the same word twice, which is rolled again; DiceList is the list numbered for dice, a line for each word with its dice and a tab, as the EFF publishes its own: for en it is the file of the EFF, byte for byte. encrypt -dice TEXT and -dice-file FILE take the words from the dice of the list -dic and show them: the words open the capsule, the numbers give them only with that list. datekeys wordlist [-dic LIST] writes the numbered list, to print it, and its SHA-256, which wordkey/lists/README.md records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>v0.15
parent
e671032e04
commit
92e7154c23
@ -0,0 +1,92 @@
|
|||||||
|
package wordkey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiceListSize is the size of a list that dice draw from: five dice, each
|
||||||
|
// from 1 to 6, give 6^5 positions. Whoever does not trust the random numbers
|
||||||
|
// of a computer rolls them, and looks the words up in the list numbered for
|
||||||
|
// dice (DiceList).
|
||||||
|
const DiceListSize = 7776
|
||||||
|
|
||||||
|
// DiceNumber returns the dice of the word at position i of a list of
|
||||||
|
// DiceListSize words: five digits from 1 to 6, each one more than a digit of
|
||||||
|
// i in base 6, the first the most significant, so that 11111 is the first
|
||||||
|
// word and 66666 the last, as in the list of the EFF.
|
||||||
|
func DiceNumber(i int) (string, error) {
|
||||||
|
if i < 0 || i >= DiceListSize {
|
||||||
|
return "", fmt.Errorf("wordkey: no dice give position %d of a list of %d words", i, DiceListSize)
|
||||||
|
}
|
||||||
|
var b [5]byte
|
||||||
|
for k := len(b) - 1; k >= 0; k-- {
|
||||||
|
b[k] = '1' + byte(i%6)
|
||||||
|
i /= 6
|
||||||
|
}
|
||||||
|
return string(b[:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiceWord returns the word of list that the dice give, as DiceNumber
|
||||||
|
// numbers the words: five digits from 1 to 6. The list must have
|
||||||
|
// DiceListSize words.
|
||||||
|
func DiceWord(list []string, dice string) (string, error) {
|
||||||
|
if len(list) != DiceListSize {
|
||||||
|
return "", fmt.Errorf("wordkey: dice draw from a list of %d words, not %d", DiceListSize, len(list))
|
||||||
|
}
|
||||||
|
if len(dice) != 5 {
|
||||||
|
return "", fmt.Errorf("wordkey: %q is not five dice: five digits from 1 to 6", dice)
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for k := range len(dice) {
|
||||||
|
c := dice[k]
|
||||||
|
if c < '1' || c > '6' {
|
||||||
|
return "", fmt.Errorf("wordkey: %q is not five dice: five digits from 1 to 6", dice)
|
||||||
|
}
|
||||||
|
i = i*6 + int(c-'1')
|
||||||
|
}
|
||||||
|
return list[i], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiceWords returns the words that dice give, in their order: one number of
|
||||||
|
// five dice for each word, separated by white space, at least MinWords of
|
||||||
|
// them, and never the same word twice, which whoever rolls rolls again.
|
||||||
|
// Fair dice draw each word as Generate does, so the words are as strong.
|
||||||
|
func DiceWords(list []string, dice string) ([]string, error) {
|
||||||
|
if len(list) != DiceListSize {
|
||||||
|
return nil, fmt.Errorf("wordkey: dice draw from a list of %d words, not %d", DiceListSize, len(list))
|
||||||
|
}
|
||||||
|
numbers := strings.Fields(dice)
|
||||||
|
if len(numbers) < MinWords {
|
||||||
|
return nil, fmt.Errorf("wordkey: a key of words needs at least %d words, not %d", MinWords, len(numbers))
|
||||||
|
}
|
||||||
|
seen := make(map[string]bool, len(numbers))
|
||||||
|
words := make([]string, 0, len(numbers))
|
||||||
|
for _, n := range numbers {
|
||||||
|
w, err := DiceWord(list, n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if seen[w] {
|
||||||
|
return nil, fmt.Errorf("wordkey: the dice %s give %q a second time; roll them again", n, w)
|
||||||
|
}
|
||||||
|
seen[w] = true
|
||||||
|
words = append(words, w)
|
||||||
|
}
|
||||||
|
return words, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiceList returns list numbered for dice, to print it: a line for each
|
||||||
|
// word, its dice, a tab and the word, as the EFF publishes its list. For the
|
||||||
|
// English list it is the file of the EFF, byte for byte.
|
||||||
|
func DiceList(list []string) (string, error) {
|
||||||
|
if len(list) != DiceListSize {
|
||||||
|
return "", fmt.Errorf("wordkey: dice draw from a list of %d words, not %d", DiceListSize, len(list))
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
for i, w := range list {
|
||||||
|
n, _ := DiceNumber(i)
|
||||||
|
b.WriteString(n + "\t" + w + "\n")
|
||||||
|
}
|
||||||
|
return b.String(), nil
|
||||||
|
}
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
package wordkey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The lists numbered for dice and their SHA-256, as lists/README.md records
|
||||||
|
// them: the English one is the file of the EFF, byte for byte.
|
||||||
|
var diceListHashes = map[string]string{
|
||||||
|
"en": "addd35536511597a02fa0a9ff1e5284677b8883b83e986e43f15a3db996b903e",
|
||||||
|
"es": "611f779a33df74587e9dfb486bb0185a9dfd4d1384e75993a21247ad31cefddb",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiceNumber(t *testing.T) {
|
||||||
|
for i, want := range map[int]string{0: "11111", 1: "11112", 5: "11116", 6: "11121", 35: "11166", 36: "11211", 1295: "16666", 1296: "21111", 7775: "66666"} {
|
||||||
|
if got, err := DiceNumber(i); err != nil || got != want {
|
||||||
|
t.Errorf("DiceNumber(%d) = %q, %v, want %q", i, got, err, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, i := range []int{-1, 7776} {
|
||||||
|
if _, err := DiceNumber(i); err == nil || err.Error() != fmt.Sprintf("wordkey: no dice give position %d of a list of 7776 words", i) {
|
||||||
|
t.Errorf("DiceNumber(%d): %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiceWord(t *testing.T) {
|
||||||
|
en, _ := List("en")
|
||||||
|
es, _ := List("es")
|
||||||
|
// Every position, there and back.
|
||||||
|
for i := range en {
|
||||||
|
n, _ := DiceNumber(i)
|
||||||
|
if w, err := DiceWord(en, n); err != nil || w != en[i] {
|
||||||
|
t.Fatalf("DiceWord(%s) = %q, %v, want %q", n, w, err, en[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range []struct {
|
||||||
|
list []string
|
||||||
|
dice, want string
|
||||||
|
}{
|
||||||
|
{en, "11111", "abacus"},
|
||||||
|
{en, "11112", "abdomen"},
|
||||||
|
{en, "35214", "jovial"},
|
||||||
|
{en, "66666", "zoom"},
|
||||||
|
{es, "11111", "abad"},
|
||||||
|
{es, "35214", "glaciar"},
|
||||||
|
{es, "66666", "útil"},
|
||||||
|
} {
|
||||||
|
if w, err := DiceWord(c.list, c.dice); err != nil || w != c.want {
|
||||||
|
t.Errorf("DiceWord(%s) = %q, %v, want %q", c.dice, w, err, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, dice := range []string{"", "1111", "111111", "11110", "11117", "a1111", "1111 ", string(rune(0xff11)) + "1111"} {
|
||||||
|
want := fmt.Sprintf("wordkey: %q is not five dice: five digits from 1 to 6", dice)
|
||||||
|
if _, err := DiceWord(en, dice); err == nil || err.Error() != want {
|
||||||
|
t.Errorf("DiceWord(%q): %v, want %q", dice, err, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := DiceWord(en[:2048], "11111"); err == nil || err.Error() != "wordkey: dice draw from a list of 7776 words, not 2048" {
|
||||||
|
t.Errorf("DiceWord of 2048 words: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiceWords(t *testing.T) {
|
||||||
|
en, _ := List("en")
|
||||||
|
words, err := DiceWords(en, " 11111 11112\t11113\n11114 11115 11116 11121 ")
|
||||||
|
if err != nil || strings.Join(words, " ") != "abacus abdomen abdominal abide abiding ability ablaze" {
|
||||||
|
t.Fatalf("DiceWords = %q, %v", words, err)
|
||||||
|
}
|
||||||
|
if err := Check(Normalize(strings.Join(words, " "))); err != nil {
|
||||||
|
t.Errorf("the words of the dice: %v", err)
|
||||||
|
}
|
||||||
|
for _, c := range []struct{ dice, want string }{
|
||||||
|
{"11111 11112 11113 11114 11115", "wordkey: a key of words needs at least 6 words, not 5"},
|
||||||
|
{"", "wordkey: a key of words needs at least 6 words, not 0"},
|
||||||
|
{"11111 11112 11113 11114 11115 1116", `wordkey: "1116" is not five dice: five digits from 1 to 6`},
|
||||||
|
{"11111 11112 11113 11114 11115 11111", `wordkey: the dice 11111 give "abacus" a second time; roll them again`},
|
||||||
|
{"11111,11112 11113 11114 11115 11116 11121", `wordkey: "11111,11112" is not five dice: five digits from 1 to 6`},
|
||||||
|
} {
|
||||||
|
if _, err := DiceWords(en, c.dice); err == nil || err.Error() != c.want {
|
||||||
|
t.Errorf("DiceWords(%q): %v, want %q", c.dice, err, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := DiceWords(en[1:], "11111 11112 11113 11114 11115 11116"); err == nil || err.Error() != "wordkey: dice draw from a list of 7776 words, not 7775" {
|
||||||
|
t.Errorf("DiceWords of 7775 words: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiceList(t *testing.T) {
|
||||||
|
for lang, want := range diceListHashes {
|
||||||
|
list, _ := List(lang)
|
||||||
|
text, err := DiceList(list)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := fmt.Sprintf("%x", sha256.Sum256([]byte(text))); got != want {
|
||||||
|
t.Errorf("DiceList(%s): SHA-256 %s, want %s", lang, got, want)
|
||||||
|
}
|
||||||
|
lines := strings.Split(strings.TrimSuffix(text, "\n"), "\n")
|
||||||
|
if len(lines) != DiceListSize || lines[0] != "11111\t"+list[0] || lines[7775] != "66666\t"+list[7775] {
|
||||||
|
t.Errorf("DiceList(%s): %d lines, %q … %q", lang, len(lines), lines[0], lines[len(lines)-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := DiceList(make([]string, 2048)); err == nil || err.Error() != "wordkey: dice draw from a list of 7776 words, not 2048" {
|
||||||
|
t.Errorf("DiceList of 2048 words: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in new issue