forked from jambolo/bip39
-
Notifications
You must be signed in to change notification settings - Fork 0
/
missing.coffee
executable file
·201 lines (184 loc) · 6.75 KB
/
missing.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# Generates all possible mnemonic phrases, given a partial mnemonic phrase.
#
# Command syntax
#
# missing [--language <language>] [--indexes <list>|--end|--any] [--count <N>] [--verbose] [--json] <mnemonic phrase>
#
# Options
#
# --any, -a Signifies that the missing words in the phrase can be at any location. (optional)
# --count, -n Total number of words in the mnemonic phrase. (default is a multiple of 3 and depends on how many
# are known)
# --end, -e Signifies that the last words in phrase are missing. (default)
# --indexes, -i List of the possible locations of the missing words. The first location is 1. Use - to end the
# list if it is not followed by another option. (optional)
# --json, -j The possible mnemonics are listed in json format. (optional)
# --language, -l The wordlist to be used. Currently, only *english* is supported and it is the default. (optional)
# --verbose, -v Displays helpful information in addition to the results. (optional)
#
# Examples:
#
# All but the last two words of a 12-word mnemonic are known:
# missing crisp three spoil enhance dial client moment melt parade aisle
#
# Two missing words could be anywhere:
# missing --any crisp three enhance dial client melt parade aisle analyst case
#
# The two missing words could be at locations 1, 3, or 7:
# missing --indexes 1 3 7 --count 12 crisp three enhance dial client melt parade aisle analyst case
#
# A count should be specified because eight known words implies a 9-word phrase and this one has 12 words:
# missing --count 12 crisp three spoil enhance dial client moment melt:
yargs = require 'yargs'
Crypto = require 'crypto'
bip39 = require './bip39'
unique = (array) ->
itemized = {}
itemized[e] = e for e in array
(value for key, value of itemized)
combinationsOf = (array, n = array.length) ->
result = []
recurse = (c, remainder, n) ->
if n > 0
for i in [0..remainder.length - n]
recurse c.concat([remainder[i]]), remainder[i + 1..], n - 1
else
result.push c
return
recurse [], array, n
result
generateTemplate = (known, indexes, count) ->
# Note: indexes are 1..count
template = []
j = 0
k = 0
for i in [1..count]
if i == indexes[j]
template.push null
++j
else
template.push known[k]
++k
template
enumerate = (template, indexes, wordlist, cb) ->
# Note: indexes are 1..count
mnemonic = template[..]
recurse = (remainingIndexes) ->
for w in wordlist
mnemonic[remainingIndexes[0] - 1] = w
if remainingIndexes.length > 1
recurse remainingIndexes[1..]
else
# console.log "Trying: #{mnemonic}"
cb mnemonic
return
recurse indexes
return
args = yargs
.usage '$0 <mnemonic..>', 'Generates all possible mnemonic phrases, given a partial mnemonic phrase.'
.help()
.version()
.option 'any', {
type: 'boolean'
alias: 'a'
conflicts: ['end', 'indexes']
describe: 'Signifies that the missing words can be at any location in the phrase.'
}
.option 'count', {
type: 'number'
alias: 'n'
describe: 'Total number of words in the mnemonic phrase.'
}
.option 'end', {
type: 'boolean'
alias: 'e'
conflicts: ['any', 'indexes']
describe: 'Signifies that the missing words are at the end of the phrase.'
}
.option 'indexes', {
type: 'array'
alias: 'i'
conflicts: ['any', 'end']
implies: 'count'
describe: 'List of the possible locations of the missing words. The first location is 1. Use - to end a
list not followed by another option.'
}
.option 'json', {
type: 'boolean'
alias: 'j'
default: false
describe: 'The possible mnemonics are listed in JSON format.'
}
.option 'language', {
type: 'string'
choices: ['english']
default: 'english'
alias: 'l'
describe: 'The wordlist to be used. Currently, only english is supported. (optional)'
}
.option 'v', {
type: 'count'
alias: 'verbose'
describe: 'Displays helpful information in addition to the results.'
}
.example [
[
'$0 crisp three spoil enhance dial client moment melt parade aisle'
'All but the last two words of a 12-word mnemonic are known.'
]
[
'$0 --any crisp three enhance dial client melt parade aisle analyst case'
'Two missing words could be anywhere.'
]
[
'$0 --indexes 1 3 7 --count 12 crisp three enhance dial client melt parade aisle analyst case'
'The two missing words could be at locations 1, 3, or 7.'
]
[
'$0 --count 12 crisp three spoil enhance dial client moment melt'
'A count should be specified because eight known words implies a 9-word phrase and this one has 12 words'
]
]
.check (argv) ->
if argv.count?
if argv.count <= 0 or argv.count % 3 != 0
throw new Error "'count' must be a positive multiple of 3"
if argv.count <= argv.mnemonic.length
throw new Error "'count' must be greater than the number of known words"
if argv.indexes?
if argv.indexes.length > argv.count
throw new Error "'count' must be greater than or equal to the number of indexes"
for i in argv.indexes
if i < 1 or i > argv.count
throw new Error "Invalid index #{i}"
return true
.argv
#console.log 'args=', JSON.stringify(args)
# Default count is the next multiple of 3 after the number of known words plus 1 unknown
count = if args.count? then args.count else Math.ceil((args.mnemonic.length + 1) / 3) * 3
# Generate the index combinations (note the first index is 1)
if args.any
indexes = [1..count]
else if args.indexes?
indexes = unique args.indexes #de-dup and sort
else # Otherwise, assume missing words are at the end (--end option)
indexes = [args.mnemonic.length + 1..count]
n = count - args.mnemonic.length
indexCombinations = combinationsOf indexes, n
# Look for all possibilities for each combination of indexes
possibilities = []
for c in indexCombinations
template = generateTemplate args.mnemonic, c, count
process.stdout.write "Trying '" + bip39.stringify(template) + "' \n" if args.verbose > 0
# Check each enumeration
enumerate template, c, bip39.wordlists[args.language], (mnemonic) ->
[ok, result] = bip39.decode mnemonic, args.language
if ok
possibilities.push bip39.stringify(mnemonic)
true
# Print the results
if args.json
process.stdout.write JSON.stringify(possibilities)
else
process.stdout.write "#{possibilities.length} possibilities:\n" if args.verbose > 0
process.stdout.write(p + '\n') for p in possibilities