-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
ottosch
committed
Jul 22, 2024
0 parents
commit eca0887
Showing
15 changed files
with
1,279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
lastseed |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# lastseed | ||
|
||
Display the possible last seed words from 11, 14, 17, 20 or 23 words. | ||
Entropy is also displayed and can be pasted into [Ian Coleman bip39](https://github.com/iancoleman/bip39) or similar tools. | ||
|
||
## Usage | ||
|
||
```bash | ||
./lastseed traffic rabbit canal shadow eternal public evil cup poem drift episode box manual sick entry original deny | ||
``` | ||
|
||
or | ||
|
||
```bash | ||
./lastseed | ||
Enter seed words: | ||
traffic rabbit canal shadow eternal public evil cup poem drift episode box manual sick entry original deny | ||
``` | ||
|
||
![Usage example](example.png) | ||
|
||
## Build | ||
|
||
```bash | ||
go build -o lastseed ./src | ||
``` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/ottosch/lastseed | ||
|
||
go 1.22.5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package bip39 | ||
|
||
import ( | ||
"crypto/sha256" | ||
"fmt" | ||
"math/big" | ||
"os" | ||
|
||
"github.com/ottosch/lastseed/src/bip39/wordlist" | ||
) | ||
|
||
// VerifyWordCount checks if the number of words matches a suitable number of bip39 seed words | ||
func VerifyWordCount(words []string) { | ||
actualCount := len(words) + 1 | ||
if actualCount < 12 || actualCount > 24 || actualCount%3 != 0 { | ||
fmt.Fprintf(os.Stderr, "%d word(s) input. Expected 11, 14, 17, 20 or 23\n", len(words)) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
// VerifyValidWords checks if all words belong to bip39 word list | ||
func VerifyValidWords(words []string) { | ||
for _, w := range words { | ||
_, found := wordlist.WordMap[w] | ||
if !found { | ||
fmt.Fprintf(os.Stderr, "Word \"%s\" is not part of the BIP39 word list\n", w) | ||
os.Exit(1) | ||
} | ||
} | ||
} | ||
|
||
// ValidChecksum performs checksum validation | ||
func ValidChecksum(fullBitstring *big.Int, checksumSize uint) bool { | ||
wordCount := int(checksumSize) * 3 | ||
|
||
withoutChecksumBits := new(big.Int).Set(fullBitstring) | ||
withoutChecksumBits.Rsh(withoutChecksumBits, checksumSize) | ||
|
||
withoutChecksumBitCount := (wordCount - 1) * 11 // we still don't have the last word here | ||
withoutChecksumByteLen := (withoutChecksumBitCount + 7) / 8 | ||
withoutChecksumBytes := make([]byte, withoutChecksumByteLen) | ||
withoutChecksumBits.FillBytes(withoutChecksumBytes) | ||
|
||
hashBits := sha256.Sum256(withoutChecksumBytes)[0] >> (8 - checksumSize) // ex 4 bits: 01010111000110001101 -> 00001000 | ||
bitmask := byte((1 << checksumSize) - 1) // 00001111 | ||
checksumBits := hashBits & bitmask // 00001000 | ||
|
||
withChecksumBitCount := wordCount*11 + int(checksumSize) // now all words + checksum | ||
withChecksumByteLen := (withChecksumBitCount + 7) / 8 | ||
withChecksumBytes := make([]byte, withChecksumByteLen) | ||
fullBitstring.FillBytes(withChecksumBytes) | ||
|
||
inputChecksumBits := withChecksumBytes[len(withChecksumBytes)-1] & bitmask | ||
return checksumBits == inputChecksumBits | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"os" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/ottosch/lastseed/src/seed" | ||
"github.com/ottosch/lastseed/src/table" | ||
) | ||
|
||
var ( | ||
spacesRegex = regexp.MustCompile(`\s{2,}`) | ||
reader = bufio.NewReader(os.Stdin) | ||
) | ||
|
||
func main() { | ||
words := readWordsFromCliArgs() | ||
for words == "" { | ||
fmt.Println("Enter seed words: ") | ||
words, _ = reader.ReadString('\n') | ||
} | ||
|
||
words = spacesRegex.ReplaceAllString(strings.TrimSpace(strings.ToLower(words)), " ") | ||
s := seed.NewSeed(words) | ||
|
||
fmt.Println() | ||
|
||
table.DrawSummary(s) | ||
table.DrawResults(s) | ||
} | ||
|
||
func readWordsFromCliArgs() string { | ||
args := os.Args | ||
if len(args) == 1 { | ||
return "" | ||
} | ||
|
||
return strings.Join(args[1:], " ") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package seed | ||
|
||
import ( | ||
"encoding/hex" | ||
"math/big" | ||
) | ||
|
||
type Result struct { | ||
bitstring *big.Int | ||
lastWord string | ||
} | ||
|
||
func NewResult(bitstring *big.Int, lastWord string) *Result { | ||
return &Result{bitstring, lastWord} | ||
} | ||
|
||
func (r *Result) Bitstring() *big.Int { | ||
return r.bitstring | ||
} | ||
|
||
func (r *Result) LastWord() string { | ||
return r.lastWord | ||
} | ||
|
||
// Entropy returns entropy in hex | ||
func (r *Result) Entropy(checksumSize uint) string { | ||
wordCount := int(checksumSize) * 3 | ||
|
||
withoutChecksumBits := new(big.Int).Set(r.bitstring) | ||
withoutChecksumBits.Rsh(withoutChecksumBits, checksumSize) | ||
|
||
bitCount := wordCount*11 - int(checksumSize) // entropy doesn't have checksum | ||
byteLen := (bitCount + 7) / 8 | ||
fbs := make([]byte, byteLen) | ||
withoutChecksumBits.FillBytes(fbs) | ||
|
||
return hex.EncodeToString(fbs) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package seed | ||
|
||
import ( | ||
"math" | ||
"math/big" | ||
"strings" | ||
|
||
"github.com/ottosch/lastseed/src/bip39" | ||
"github.com/ottosch/lastseed/src/bip39/wordlist" | ||
) | ||
|
||
type Seed struct { | ||
bitstring *big.Int // the bits of the seed words | ||
words []string | ||
results []*Result | ||
} | ||
|
||
func NewSeed(wordsStr string) *Seed { | ||
words := strings.Split(wordsStr, " ") | ||
|
||
bip39.VerifyWordCount(words) | ||
bip39.VerifyValidWords(words) | ||
|
||
s := &Seed{words: words} | ||
s.fillBitstring(words) | ||
|
||
s.calcLastWords() | ||
return s | ||
} | ||
|
||
func (s *Seed) GetWords() []string { | ||
return s.words | ||
} | ||
|
||
func (s *Seed) GetWordCount() int { | ||
return len(s.words) + 1 | ||
} | ||
|
||
func (s *Seed) GetChecksumSize() uint { | ||
return uint(s.GetWordCount() / 3) | ||
} | ||
|
||
func (s *Seed) GetBitstring() *big.Int { | ||
return new(big.Int).Set(s.bitstring) | ||
} | ||
|
||
func (s *Seed) GetResults() []*Result { | ||
return s.results | ||
} | ||
|
||
func (s *Seed) calcLastWords() { | ||
totalWords := int(math.Pow(2, float64(11-s.GetChecksumSize()))) | ||
results := make([]*Result, 0, totalWords) | ||
|
||
for i, bipWord := range wordlist.Wordlist { | ||
fullBitstring := addWordToBitstring(s.GetBitstring(), i) | ||
if bip39.ValidChecksum(fullBitstring, s.GetChecksumSize()) { | ||
results = append(results, NewResult(fullBitstring, bipWord)) | ||
} | ||
} | ||
|
||
s.results = results | ||
} | ||
|
||
// fillBitstring sets the seed bit string according to the words indices | ||
func (s *Seed) fillBitstring(words []string) { | ||
num := new(big.Int) | ||
for _, w := range words { | ||
index := int64(wordlist.WordMap[w]) | ||
indexBig := new(big.Int).SetInt64(index) | ||
num.Or(num.Lsh(num, 11), indexBig) // num << 11 | index | ||
} | ||
|
||
s.bitstring = num | ||
} | ||
|
||
// addWordToBitstring adds the word index to the current bit string | ||
func addWordToBitstring(bitstring *big.Int, wordIndex int) *big.Int { | ||
fullBitstring := new(big.Int).Set(bitstring) | ||
lastWordIndex := new(big.Int).SetInt64(int64(wordIndex)) | ||
fullBitstring.Or(fullBitstring.Lsh(fullBitstring, 11), lastWordIndex) | ||
return fullBitstring | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package table | ||
|
||
const ( | ||
topLeft = '┌' | ||
topSeparator = '┬' | ||
topRight = '┐' | ||
bottomLeft = '└' | ||
bottomSeparator = '┴' | ||
bottomRight = '┘' | ||
middleLeft = '├' | ||
middleSeparator = '┼' | ||
middleRight = '┤' | ||
vertical = '│' | ||
horizontal = '─' | ||
|
||
wordStr = "Word" | ||
entropyStr = "Entropy" | ||
|
||
ALIGN_LEFT = 1 << iota // 1 << 0 == 1 | ||
ALIGN_CENTER // 1 << 1 == 2 | ||
ALIGN_RIGHT // 1 << 2 == 4 | ||
|
||
BORDER_TOP | ||
BORDER_BOTTOM | ||
BORDER_MIDDLE | ||
TEXT_MIDDLE | ||
) | ||
|
||
var ( | ||
top lineType = 1 | ||
bottom lineType = 2 | ||
middle lineType = 3 | ||
textMiddle lineType = 4 | ||
|
||
alignLeft alignType = 1 | ||
alignCenter alignType = 2 | ||
alignRight alignType = 3 | ||
) | ||
|
||
type lineType int | ||
|
||
type alignType int |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package table | ||
|
||
import ( | ||
"log" | ||
"strings" | ||
) | ||
|
||
// TableCell a single table cell and its text | ||
type TableCell struct { | ||
text string | ||
} | ||
|
||
// NewCell creates a table cell, content aligned | ||
func NewCell(text string, length int, align *TextAlign) *TableCell { | ||
cellText := align.AlignText(text, length) | ||
return &TableCell{text: cellText} | ||
} | ||
|
||
// TableRow a table row, composed of various, styled cells | ||
type TableRow struct { | ||
cells []*TableCell | ||
style *lineStyle | ||
} | ||
|
||
// TextRow creates a row of text cells | ||
func TextRow(texts []string, sizes []int, settings int) *TableRow { | ||
if len(texts) != len(sizes) || len(texts)%2 != 0 { | ||
log.Fatalf("invalid number of columns: %d, %d\n", len(texts), len(sizes)) | ||
} | ||
|
||
cellSettings := parseSettings(settings) | ||
cells := make([]*TableCell, len(texts)) | ||
|
||
for i, text := range texts { | ||
cells[i] = NewCell(text, sizes[i], cellSettings.Align) | ||
} | ||
|
||
return &TableRow{cells: cells, style: cellSettings.Style} | ||
} | ||
|
||
// GridRow creates a row of grid (border) content only | ||
func GridRow(sizes []int, settings int) *TableRow { | ||
if len(sizes)%2 != 0 { | ||
log.Fatalf("invalid number of grid cells: %v\n", sizes) | ||
} | ||
|
||
cellSettings := parseSettings(settings) | ||
cells := make([]*TableCell, len(sizes)) | ||
|
||
for i, size := range sizes { | ||
cells[i] = &TableCell{text: strings.Repeat(string(horizontal), size)} | ||
} | ||
|
||
return &TableRow{cells: cells, style: cellSettings.Style} | ||
} | ||
|
||
func (g *TableRow) String() string { | ||
var result strings.Builder | ||
|
||
strList := make([]string, 0, len(g.cells)/2) | ||
for i := 0; i < len(g.cells); i += 2 { | ||
var sb strings.Builder | ||
sb.WriteRune(g.style.leftBorder) | ||
sb.WriteString(g.cells[i].text) | ||
sb.WriteRune(g.style.separator) | ||
sb.WriteString(g.cells[i+1].text) | ||
sb.WriteRune(g.style.rightBorder) | ||
strList = append(strList, sb.String()) | ||
} | ||
|
||
result.WriteString(strings.Join(strList, " ")) | ||
result.WriteRune('\n') | ||
return result.String() | ||
} |
Oops, something went wrong.