Last active
April 11, 2017 08:54
-
-
Save dabura667/49c35c598f3b03a46eda46e48f9b8668 to your computer and use it in GitHub Desktop.
js for recovering partially know phrases for BIP44
This file contains hidden or 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
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