Skip to content

Instantly share code, notes, and snippets.

@dabura667
Last active April 11, 2017 08:54
Show Gist options
  • Save dabura667/49c35c598f3b03a46eda46e48f9b8668 to your computer and use it in GitHub Desktop.
Save dabura667/49c35c598f3b03a46eda46e48f9b8668 to your computer and use it in GitHub Desktop.
js for recovering partially know phrases for BIP44
var Mnemonic = require('bitcore-mnemonic')
var fs = require('fs')
var insightRestClient = require('insight-cli').RestClient
var insightRest = new insightRestClient();
// put your word parts here separated by space
var wordParts = "exo dev ank rot loc arr gaz tal rea aba kid sig"
// Skip trying this many valid phrases.
var skipROWS = 378600
// in case you get rate limited, you can try tomorrow and skip the number of
// transaction balance queries that you did today and start off where you left off.
var skipCount = 0
if (wordParts.split(" ").length !== 12) throw new Error("Phrases for Copay are 12 words long. Your phrase is " + wordParts.split(" ").length)
if (skipCount < 0) throw new Error("Don't make the skip count negative.")
var START_TIME = new Date().getTime()
var CURRENT_TIME = START_TIME
var CSV_ONLY = false
var CSV_COUNTER = 0
var CSV_ADDRESS_STRING = "Number,Address List\r\n"
var CSV_PHRASE_STRING = "Number,\"Recovery Phrase for word parts: \"\"" + wordParts + "\"\"\"\r\n"
var USE_INPUT_FILE = false
var TIMEOUT = 2000
var TIMEOUT_COUNTER = 0
var FIND_ADDRESS = null
var OVERALL_COUNTER = 0
if (process.argv.indexOf("--help") !== -1 || process.argv.indexOf("-h") !== -1) {
console.log("%s" + "\r\n" +
"Generates a list of valid mnemonic phrases from a given phrase using partial" + "\r\n" +
"words that contain the beginning letters of each word." + "\r\n" +
"" + "\r\n" +
" -h --help This help message" + "\r\n" +
" -c --csv Output list of addresses and list of phrases to two CSV files" + "\r\n" +
" phrases.csv and addresses.csv" + "\r\n" +
" -i --input-file Reads the addresses from addresses.csv and only" + "\r\n" +
" queries insight and returns the number of the list." + "\r\n" +
" -t <ms> set the amount of time to wait for each request" + "\r\n" +
" which might help to prevent blacklisting. (Default: 1000)" + "\r\n" +
" -s <count> change the skipCount variable (Default: 0)" + "\r\n" +
" -a <address> Stop searching when you found this address.", process.argv[0].replace(/.*[/\\]([^/\\]+)$/,"$1")
+ " " + process.argv[1].replace(/.*[/\\]([^/\\]+)$/,"$1"))
process.exit(0)
}
if (process.argv.indexOf("--csv") !== -1 || process.argv.indexOf("-c") !== -1) {
CSV_ONLY = true
}
if (process.argv.indexOf("-t") !== -1) {
TIMEOUT = parseInt(process.argv[process.argv.indexOf("-t") + 1])
}
if (process.argv.indexOf("-s") !== -1) {
skipCount = parseInt(process.argv[process.argv.indexOf("-s") + 1])
}
if (process.argv.indexOf("-a") !== -1) {
FIND_ADDRESS = process.argv[process.argv.indexOf("-a") + 1]
}
if (process.argv.indexOf("--input-file") !== -1 || process.argv.indexOf("-i") !== -1) {
console.log("Reading from files...")
var addrFileData = fs.readFileSync('addresses.csv', 'utf8')
var addrRowDatas = addrFileData.toString('utf8').split("\r\n")
addrRowDatas.forEach(function(row, index) {
if (index > skipCount && index < addrRowDatas.length - 1) {
runInsightWithTimeout(
row.split('"')[1],
new Mnemonic("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon angle"),
index,
TIMEOUT * (TIMEOUT_COUNTER++))
}
})
} else {
var wordPartsArray = wordParts.split(" ")
var candidates = []
var indices = [0,0,0,0,0,0,0,0,0,0,0,0]
var validMnemonics = []
wordPartsArray.forEach(function (wordPart) {
candidates.push(Mnemonic.Words.ENGLISH.filter(function (word) {
return word.slice(0, wordPart.length) == wordPart
}))
if (candidates[candidates.length - 1].length == 0) candidates[candidates.length - 1] = Mnemonic.Words.ENGLISH
})
var candidatesCounts = candidates.map(function (item) { return item.length > 0 ? item.length : 2048 })
var totalPossibilities = candidatesCounts.reduce(function (a, b) { return a * b })
console.log("Possibilities for each word: " + candidatesCounts.toString())
console.log("Iterating over " + totalPossibilities + " possibilities...")
if (CSV_ONLY) console.log("Number of Valid phrases should be around " + Math.floor(totalPossibilities / 16))
var finished = false
var correctPhrase = ""
function callInsight (addresses, myMN, validPhraseCounter) {
if (!CSV_ONLY) {
return insightRest.tx4addrs(addresses, {}).then(function (result) {
var callbackMN = myMN
if (result.totalItems > 0) {
console.log("Found transactions on mnemonic #" + validPhraseCounter + ": " + callbackMN.phrase)
console.log("THIS IS YOUR BACKUP PHRASE!!!")
console.log(callbackMN.phrase)
finished = true
correctPhrase = callbackMN.phrase
process.exit(0)
} else {
console.log("No transactions on mnemonic #" + validPhraseCounter + ": " + callbackMN.phrase)
}
})
.catch(function (err) {
console.log(err)
})
}
}
function runInsightWithTimeout (addrs, mn, idx, timeout) {
if (!CSV_ONLY) {
setTimeout(function() {
console.log("Sent Request #" + idx)
return callInsight(addrs, mn, idx)
}, timeout)
}
}
function getRunTime() {
CURRENT_TIME = new Date().getTime()
var delta = CURRENT_TIME - START_TIME
var hours = Math.floor(delta / (60*60*1000))
var minutes = Math.floor(delta / (60*1000)) - (hours * 60)
var seconds = Math.floor(delta / (1000)) - (hours * 60 * 60) - (minutes * 60)
var milliseconds = delta - (hours * 60 * 60 * 1000) - (minutes * 60 * 1000) - (seconds * 1000)
return ("0"+hours).slice(-2) + ":" + ("0"+minutes).slice(-2) + ":" + ("0"+seconds).slice(-2) + "." + ("00"+milliseconds).slice(-3)
}
function tickIndicesUpOne(indxs, counts) {
var roomToCount = false
for (var i = 0; i < indxs.length; i++) {
if (indxs[i] < counts[i] - 1) {
roomToCount = true
}
}
if (roomToCount) {
var ticked = false
for (var i = indxs.length - 1; i >= 0; i--) {
if (!ticked) {
if (indxs[i] >= counts[i] - 1) {
indxs[i] = 0
} else {
indxs[i] += 1
ticked = true
}
}
}
}
}
skipROWS *= 14
while(skipROWS) {
OVERALL_COUNTER++
tickIndicesUpOne(indices, candidatesCounts)
skipROWS--
}
while (!finished) {
//console.log(indices.map(function (item) { return item + 1 }))
//console.log(candidatesCounts)
var currentPhrase = candidates.map(function(wordArray, index) {
return wordArray[indices[index]]
}).join(" ")
OVERALL_COUNTER++
//console.log("Trying phrase: " + currentPhrase)
try {
var currentMN = new Mnemonic(currentPhrase)
} catch (e) {
//console.log("Not valid mnemonic: " + currentPhrase)
currentMN = null
}
if (currentMN) {
validMnemonics.push((validMnemonics.length + 1) + ": " + currentMN.phrase)
var receiveKeys = currentMN.toHDPrivateKey().derive("m/44'/0'/0'/0")
var addresses = []
for (var i = 0; i < 20; i++) {
addresses.push(receiveKeys.derive(i).publicKey.toAddress().toString())
}
if (CSV_ONLY) {
CSV_COUNTER++
if (CSV_COUNTER % 100 == 0) console.log(getRunTime()
+ ": Saving row #" + CSV_COUNTER
+ " (Total: #" + OVERALL_COUNTER
+ ") (indices: [" + indices + "])" )
//CSV_PHRASE_STRING += validMnemonics.length + "," + currentMN.phrase + "\r\n"
//CSV_ADDRESS_STRING += validMnemonics.length + ',"' + addresses + '"\r\n'
if (FIND_ADDRESS && addresses.indexOf(FIND_ADDRESS) != -1) {
//fs.writeFileSync('addresses.csv', CSV_ADDRESS_STRING)
//fs.writeFileSync('phrases.csv', CSV_PHRASE_STRING)
//console.log('wrote CSVs to disk')
}
}
if (FIND_ADDRESS && addresses.indexOf(FIND_ADDRESS) != -1) {
console.log('The following phrase (#' + validMnemonics.length + ') contains address: ' + FIND_ADDRESS)
console.log(currentMN.phrase)
process.exit(0)
}
if (!skipCount) {
runInsightWithTimeout(addresses.join(","), currentMN, validMnemonics.length, TIMEOUT * (TIMEOUT_COUNTER++))
} else {
skipCount--
}
}
if (candidatesCounts.toString() !== indices.map(function (item) { return item + 1 }).toString()) {
tickIndicesUpOne(indices, candidatesCounts)
} else {
finished = true
correctPhrase = ""
}
}
if (!CSV_ONLY) {
console.log("List of valid mnemonics:")
console.log(validMnemonics.join("\r\n"))
console.log("WAITING ON RESPONSE FROM SERVER FOR BALANCES... THE ONE WITH A BALANCE IS YOURS.")
} else {
//fs.writeFileSync('addresses.csv', CSV_ADDRESS_STRING)
//fs.writeFileSync('phrases.csv', CSV_PHRASE_STRING)
//console.log('wrote CSVs to disk')
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment