Move from private repo

This commit is contained in:
Ian Coleman
2014-09-18 11:01:19 +10:00
commit ebd8d4e8b5
7 changed files with 45883 additions and 0 deletions

22946
bip39-standalone.html Normal file

File diff suppressed because one or more lines are too long

27
readme.md Normal file
View File

@@ -0,0 +1,27 @@
# BIP39 Tool
A tool for converting BIP39 mnemonic phrases to addresses and private keys.
## Online Version
Coming soon
## Standalone offline version
Download `bip39-standalone.html`
Open the file in a browser by double clicking it.
## Usage
Enter your BIP39 phrase into the 'BIP39 Phrase' field, or press
'Generate Random Phrase'
If required, set the derivation path, although the defaults are quite usable.
See the table for a list of addresses generated from the phrase.
Toggle columns to blank to easily copy/paste a single column of data, eg to
import private keys into a wallet or supply someone with a list of addresses.
The BIP32 keys can be used at [bip32.org](https://bip32.org) if desired.

257
src/index.html Normal file
View File

@@ -0,0 +1,257 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<title>BIP39 - Mnemonic Code</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<meta content="Mnemonic code for generating deterministic keys" name="description"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<meta content="bitcoin mnemonic converter" name="description" />
<meta content="DC POS" name="author" />
<style>
.feedback-container {
position: fixed;
top: 0;
width: 100%;
text-align: center;
z-index: 4;
}
.feedback {
display: table;
padding: 0.5em 1em;
background-color: orange;
margin: 0 auto;
font-size: 2em;
color: #444;
border: 2px solid #555;
border-top: 0;
border-bottom-left-radius: 20px 20px;
border-bottom-right-radius: 20px 20px;
}
</style>
</head>
<body>
<div class="container">
<h1 class="text-center">Mnemonic Code Converter</h1>
<hr>
<div class="row">
<div class="col-md-12">
<h2>Phrase</h2>
<form class="form-horizontal" role="form">
<div class="col-sm-2"></div>
<div class="col-sm-10">
<p>For more info see the <a href="https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki" target="_blank">BIP39 spec</a></p>
</div>
<div class="form-group">
<label for="phrase" class="col-sm-2 control-label">BIP39 Phrase</label>
<div class="col-sm-10">
<textarea id="phrase" class="phrase form-control"></textarea>
</div>
</div>
<div class="form-group">
<label for="strength" class="col-sm-2 control-label">Number of words</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="strength form-control" id="strength" value="12">
<span class="input-group-btn">
<button class="btn generate">Generate Random Phrase</button>
</span>
</div>
</div>
</div>
<div class="form-group">
<label for="root-key" class="col-sm-2 control-label">BIP32 Root Key</label>
<div class="col-sm-10">
<textarea id="root-key" class="root-key form-control" disabled="disabled"></textarea>
</div>
</div>
</form>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-12">
<h2>Derivation Path</h2>
<ul class="derivation-type nav nav-tabs" role="tablist">
<li class="active">
<a href="#bip44" role="tab" data-toggle="tab">BIP44</a></li>
<li><a href="#bip32" role="tab" data-toggle="tab">BIP32</a></li>
</ul>
<div class="derivation-type tab-content">
<div id="bip44" class="tab-pane active">
<form class="form-horizontal" role="form">
<br>
<div class="col-sm-2"></div>
<div class="col-sm-10">
<p>For more info see the <a href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki" target="_blank">BIP44 spec</a></p>
</div>
<div class="form-group">
<label for="purpose" class="col-sm-2 control-label">
<a href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#purpose" target="_blank">Purpose</a>
</label>
<div class="col-sm-10">
<input id="purpose" type="text" class="purpose form-control" value="44">
</div>
</div>
<div class="form-group">
<label for="coin" class="col-sm-2 control-label">
<a href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#registered-coin-types" target="_blank">Coin</a>
</label>
<div class="col-sm-10">
<input id="coin" type="text" class="coin form-control" value="0">
</div>
</div>
<div class="form-group">
<label for="account" class="col-sm-2 control-label">
<a href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account" target="_blank">Account</a>
</label>
<div class="col-sm-10">
<input id="account" type="text" class="account form-control" value="0">
</div>
</div>
<div class="form-group">
<label for="change" class="col-sm-2 control-label">
<a href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#change" target="_blank">External / Internal</a>
</label>
<div class="col-sm-10">
<input id="change" type="text" class="change form-control" value="0">
</div>
</div>
<div class="form-group">
<label for="bip44-path" class="col-sm-2 control-label">BIP32 Derivation Path</label>
<div class="col-sm-10">
<input id="bip44-path" type="text" class="path form-control" value="m/44'/0'/0'/0" disabled="disabled">
</div>
</div>
</form>
</div>
<div id="bip32" class="tab-pane">
<form class="form-horizontal" role="form">
<br>
<div class="col-sm-2"></div>
<div class="col-sm-10">
<p>For more info see the <a href="https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki" target="_blank">BIP32 spec</a></p>
</div>
<div class="form-group">
<label for="bip32-path" class="col-sm-2 control-label">BIP32 Derivation Path</label>
<div class="col-sm-10">
<input id="bip32-path" type="text" class="path form-control" value="m/0">
</div>
</div>
</form>
</div>
</div>
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="extended-priv-key" class="col-sm-2 control-label">BIP32 Extended Key</label>
<div class="col-sm-10">
<textarea id="extended-priv-key" class="extended-priv-key form-control" disabled="disabled"></textarea>
</div>
</div>
<div class="form-group">
<label for="extended-pub-key" class="col-sm-2 control-label">BIP32 Extended Key (addresses only)</label>
<div class="col-sm-10">
<textarea id="extended-pub-key" class="extended-pub-key form-control" disabled="disabled"></textarea>
</div>
</div>
</form>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-12">
<h2>Derived Addresses</h2>
<p>Note these addreses are derived from the <strong>BIP32 Extended Key</strong></p>
<table class="table table-striped">
<thead>
<th>
<div class="input-group">
Index&nbsp;&nbsp;
<button class="index-toggle">Toggle</button>
</div>
</th>
<th>
<div class="input-group">
Address&nbsp;&nbsp;
<button class="address-toggle">Toggle</button>
</div>
</th>
<th>
<div class="input-group">
Private Key&nbsp;&nbsp;
<button class="private-key-toggle">Toggle</button>
</div>
</th>
</thead>
<tbody class="addresses">
<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>
</tbody>
</table>
</div>
</div>
<span>Show next </button>
<input type="number" class="rows-to-add" value="20">
<button class="more">Show</button>
<hr>
<div class="row">
<div class="col-md-12">
<h2>More info</h2>
<h3>BIP39 <span class="small">Mnemonic code for generating deterministic keys</span></h3>
<p>
Read more at the
<a href="https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki">official BIP39 spec</a>
</p>
<h3>BIP32 <span class="small">Hierarchical Deterministic Wallets</span></h3>
<p>
Read more at the
<a href="https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki" target="_blank">official BIP32 spec</a>
and see the demo at
<a href="http://bip32.org/" target="_blank">bip32.org</a>
</p>
<h3>BIP44 <span class="small">Multi-Account Hierarchy for Deterministic Wallets</span></h3>
<p>
Read more at the
<a href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki" target="_blank">official BIP44 spec</a>
</p>
<h3>Private Keys</h3>
<p>
Use private keys at
<a href="https://brainwallet.github.io/" target="_blank">brainwallet.org</a>,
but be careful - it can be easy to make mistakes if you
don't know what you're doing
</p>
</div>
</div>
</div>
<div class="feedback-container">
<div class="feedback"></div>
</div>
<script type="text/template" id="address-row-template">
<tr>
<td class="index"><span></span></td>
<td class="address"><span></span></td>
<td class="privkey"><span></span></td>
</tr>
</script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="/js/bitcoinjs-1-0-0.js"></script>
<script src="/js/asmcrypto.js"></script>
<script src="/js/jsbip39.js"></script>
<script src="/js/index.js"></script>
</body>
</html>

7467
src/js/asmcrypto.js Normal file

File diff suppressed because one or more lines are too long

14463
src/js/bitcoinjs-1-0-0.js Normal file

File diff suppressed because it is too large Load Diff

337
src/js/index.js Normal file
View File

@@ -0,0 +1,337 @@
(function() {
var mnemonic = new Mnemonic("english");
var bip32RootKey = null;
var bip32ExtendedKey = null;
var network = Bitcoin.networks.bitcoin;
var addressRowTemplate = $("#address-row-template");
var phraseChangeTimeoutEvent = null;
var DOM = {};
DOM.phrase = $(".phrase");
DOM.generate = $(".generate");
DOM.rootKey = $(".root-key");
DOM.extendedPrivKey = $(".extended-priv-key");
DOM.extendedPubKey = $(".extended-pub-key");
DOM.bip32path = $("#bip32-path");
DOM.bip44path = $("#bip44-path");
DOM.bip44purpose = $("#bip44 .purpose");
DOM.bip44coin = $("#bip44 .coin");
DOM.bip44account = $("#bip44 .account");
DOM.bip44change = $("#bip44 .change");
DOM.strength = $(".strength");
DOM.addresses = $(".addresses");
DOM.rowsToAdd = $(".rows-to-add");
DOM.more = $(".more");
DOM.feedback = $(".feedback");
DOM.tab = $(".derivation-type a");
DOM.indexToggle = $(".index-toggle");
DOM.addressToggle = $(".address-toggle");
DOM.privateKeyToggle = $(".private-key-toggle");
var derivationPath = DOM.bip44path.val();
function init() {
// Events
DOM.phrase.on("keyup", delayedPhraseChanged);
DOM.generate.on("click", generateClicked);
DOM.more.on("click", showMore);
DOM.bip32path.on("keyup", bip32Changed);
DOM.bip44purpose.on("keyup", bip44Changed);
DOM.bip44coin.on("keyup", bip44Changed);
DOM.bip44account.on("keyup", bip44Changed);
DOM.bip44change.on("keyup", bip44Changed);
DOM.tab.on("click", tabClicked);
DOM.indexToggle.on("click", toggleIndexes);
DOM.addressToggle.on("click", toggleAddresses);
DOM.privateKeyToggle.on("click", togglePrivateKeys);
disableForms();
hidePending();
hideValidationError();
}
// Event handlers
function delayedPhraseChanged() {
hideValidationError();
showPending();
if (phraseChangeTimeoutEvent != null) {
clearTimeout(phraseChangeTimeoutEvent);
}
phraseChangeTimeoutEvent = setTimeout(phraseChanged, 400);
}
function phraseChanged() {
showPending();
hideValidationError();
// Get the mnemonic phrase
var phrase = DOM.phrase.val();
var errorText = findPhraseErrors(phrase);
if (errorText) {
showValidationError(errorText);
return;
}
// Get the derivation path
var errorText = findDerivationPathErrors();
if (errorText) {
showValidationError(errorText);
return;
}
// Calculate and display
calcBip32Seed(phrase, derivationPath);
displayBip32Info();
hidePending();
}
function generateClicked() {
clearDisplay();
showPending();
setTimeout(function() {
var phrase = generateRandomPhrase();
if (!phrase) {
return;
}
phraseChanged();
}, 50);
}
function tabClicked(e) {
var activePath = $(e.target.getAttribute("href") + " .path");
derivationPath = activePath.val();
derivationChanged();
}
function derivationChanged() {
delayedPhraseChanged();
}
function bip32Changed() {
derivationPath = DOM.bip32path.val();
derivationChanged();
}
function bip44Changed() {
setBip44DerivationPath();
derivationPath = DOM.bip44path.val();
derivationChanged();
}
function toggleIndexes() {
$("td.index span").toggleClass("invisible");
}
function toggleAddresses() {
$("td.address span").toggleClass("invisible");
}
function togglePrivateKeys() {
$("td.privkey span").toggleClass("invisible");
}
// Private methods
function generateRandomPhrase() {
if (!hasStrongRandom()) {
var errorText = "This browser does not support strong randomness";
showValidationError(errorText);
return;
}
var numWords = parseInt(DOM.strength.val());
// Check strength is an integer
if (isNaN(numWords)) {
DOM.strength.val("12");
numWords = 12;
}
// Check strength is a multiple of 32, if not round it down
if (numWords % 3 != 0) {
numWords = Math.floor(numWords / 3) * 3;
DOM.strength.val(numWords);
}
// Check strength is at least 32
if (numWords == 0) {
numWords = 3;
DOM.strength.val(numWords);
}
var strength = numWords / 3 * 32;
var words = mnemonic.generate(strength);
DOM.phrase.val(words);
return words;
}
function calcBip32Seed(phrase, path) {
var seed = mnemonic.toSeed(phrase);
var seedHash = Bitcoin.crypto.sha256(seed).toString("hex");
bip32RootKey = Bitcoin.HDNode.fromSeedHex(seedHash, network);
bip32ExtendedKey = bip32RootKey;
// Derive the key from the path
var pathBits = path.split("/");
for (var i=0; i<pathBits.length; i++) {
var bit = pathBits[i];
var index = parseInt(bit);
if (isNaN(index)) {
continue;
}
var hardened = bit[bit.length-1] == "'";
if (hardened) {
bip32ExtendedKey = bip32ExtendedKey.deriveHardened(index);
}
else {
bip32ExtendedKey = bip32ExtendedKey.derive(index);
}
}
}
function showValidationError(errorText) {
DOM.feedback
.text(errorText)
.show();
}
function hideValidationError() {
DOM.feedback
.text("")
.hide();
}
function findPhraseErrors(phrase) {
// TODO make this right
// Preprocess the words
var parts = phrase.split(" ");
var proper = [];
for (var i=0; i<parts.length; i++) {
var part = parts[i];
if (part.length > 0) {
// TODO check that lowercasing is always valid to do
proper.push(part.toLowerCase());
}
}
// TODO some levenstein on the words
var properPhrase = proper.join(' ');
// Check the words are valid
var isValid = mnemonic.check(properPhrase);
if (!isValid) {
return "Invalid mnemonic";
}
return false;
}
function findDerivationPathErrors(path) {
// TODO
return false;
}
function displayBip32Info() {
// Display the key
var rootKey = bip32RootKey.toBase58();
DOM.rootKey.val(rootKey);
var extendedPrivKey = bip32ExtendedKey.toBase58();
DOM.extendedPrivKey.val(extendedPrivKey);
var extendedPubKey = bip32ExtendedKey.toBase58(false);
DOM.extendedPubKey.val(extendedPubKey);
// Display the addresses and privkeys
clearAddressesList();
displayAddresses(0, 20);
}
function displayAddresses(start, total) {
for (var i=0; i<total; i++) {
var index = i+ start;
var key = bip32ExtendedKey.derive(index);
var address = key.getAddress().toString();
var privkey = key.privKey.toWIF();
addAddressToList(index, address, privkey);
}
}
function showMore() {
var start = DOM.addresses.children().length;
var rowsToAdd = parseInt(DOM.rowsToAdd.val());
if (isNaN(rowsToAdd)) {
rowsToAdd = 20;
DOM.rowsToAdd.val("20");
}
if (rowsToAdd > 200) {
var msg = "Generating " + rowsToAdd + " rows could take a while. ";
msg += "Do you want to continue?";
if (!confirm(msg)) {
return;
}
}
showPending();
setTimeout(function() {
displayAddresses(start, rowsToAdd);
hidePending();
}, 50);
}
function clearDisplay() {
clearAddressesList();
clearKey();
hideValidationError();
}
function clearAddressesList() {
DOM.addresses.empty();
}
function clearKey() {
DOM.rootKey.val("");
DOM.extendedPrivKey.val("");
DOM.extendedPubKey.val("");
}
function addAddressToList(index, address, privkey) {
var row = $(addressRowTemplate.html());
row.find(".index span").text(index);
row.find(".address span").text(address);
row.find(".privkey span").text(privkey);
DOM.addresses.append(row);
}
function hasStrongRandom() {
return 'crypto' in window && window['crypto'] !== null;
}
function disableForms() {
$("form").on("submit", function(e) {
e.preventDefault();
});
}
function setBip44DerivationPath() {
var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44);
var coin = parseIntNoNaN(DOM.bip44coin.val(), 0);
var account = parseIntNoNaN(DOM.bip44account.val(), 0);
var change = parseIntNoNaN(DOM.bip44change.val(), 0);
var path = "m/";
path += purpose + "'/";
path += coin + "'/";
path += account + "'/";
path += change;
DOM.bip44path.val(path);
}
function parseIntNoNaN(val, defaultVal) {
var v = parseInt(val);
if (isNaN(v)) {
return defaultVal;
}
return v;
}
function showPending() {
DOM.feedback
.text("Calculating...")
.show();
}
function hidePending() {
DOM.feedback
.text("")
.hide();
}
init();
})();

386
src/js/jsbip39.js Normal file
View File

@@ -0,0 +1,386 @@
/*
* Copyright (c) 2013 Pavol Rusnak
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Javascript port from python by Ian Coleman
*
* Includes code from asmCrypto
* https://github.com/tresorit/asmcrypto.js
*/
var Mnemonic = function(language) {
var PBKDF2_ROUNDS = 2048;
var RADIX = 2048;
var self = this;
var wordlist = [];
function init() {
wordlist = WORDLISTS[language];
if (wordlist.length != RADIX) {
err = 'Wordlist should contain ' + RADIX + ' words, but it contains ' + wordlist.length + ' words.';
throw err;
}
}
self.generate = function(strength) {
strength = strength || 128;
var r = strength % 32;
if (r > 0) {
throw 'Strength should be divisible by 32, but it is not (' + r + ').';
}
var hasStrongCrypto = 'crypto' in window && window['crypto'] !== null;
if (!hasStrongCrypto) {
throw 'Mnemonic should be generated with strong randomness, but crypto.getRandomValues is unavailable';
}
var buffer = new Uint8Array(strength / 8);
var data = crypto.getRandomValues(buffer);
return self.toMnemonic(data);
}
self.toMnemonic = function(data) {
if (data.length % 4 > 0) {
throw 'Data length in bits should be divisible by 32, but it is not (' + data.length + ' bytes = ' + data.length*8 + ' bits).'
}
//h = hashlib.sha256(data).hexdigest()
var uintArray = new Uint8Array(data);
var h = asmCrypto.SHA256.bytes(uintArray);
// b is a binary string, eg '00111010101100...'
//b = bin(int(binascii.hexlify(data), 16))[2:].zfill(len(data) * 8) + \
// bin(int(h, 16))[2:].zfill(256)[:len(data) * 8 / 32]
//
// a = bin(int(binascii.hexlify(data), 16))[2:].zfill(len(data) * 8)
// c = bin(int(h, 16))[2:].zfill(256)
// d = c[:len(data) * 8 / 32]
var a = byteArrayToBinaryString(data);
var c = byteArrayToBinaryString(h);
var d = c.substring(0, data.length * 8 / 32);
// b = line1 + line2
var b = a + d;
var result = [];
var blen = b.length / 11;
for (var i=0; i<blen; i++) {
var idx = parseInt(b.substring(i * 11, (i + 1) * 11), 2);
result.push(wordlist[idx]);
}
return result.join(' ');
}
self.check = function(mnemonic) {
var mnemonic = mnemonic.split(' ')
if (mnemonic.length % 3 > 0) {
return false
}
// idx = map(lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic)
var idx = [];
for (var i=0; i<mnemonic.length; i++) {
var word = mnemonic[i];
var wordIndex = wordlist.indexOf(word);
if (wordIndex == -1) {
return false;
}
var binaryIndex = zfill(wordIndex.toString(2), 11);
idx.push(binaryIndex);
}
var b = idx.join('');
var l = b.length;
//d = b[:l / 33 * 32]
//h = b[-l / 33:]
var d = b.substring(0, l / 33 * 32);
var h = b.substring(l - l / 33, l);
//nd = binascii.unhexlify(hex(int(d, 2))[2:].rstrip('L').zfill(l / 33 * 8))
//nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[:l / 33]
var nd = binaryStringToByteArray(d);
var ndHash = asmCrypto.SHA256.bytes(nd);
var ndBstr = zfill(byteArrayToBinaryString(ndHash), 256);
var nh = ndBstr.substring(0,l/33);
return h == nh;
}
self.toSeed = function(mnemonic, passphrase) {
passphrase = passphrase || '';
mnemonic = normalizeString(mnemonic)
passphrase = normalizeString(passphrase)
passphrase = "mnemonic" + passphrase;
//return PBKDF2(mnemonic, 'mnemonic' + passphrase, iterations=PBKDF2_ROUNDS, macmodule=hmac, digestmodule=hashlib.sha512).read(64)
return asmCrypto.PBKDF2_HMAC_SHA512.hex(mnemonic, passphrase, PBKDF2_ROUNDS, 512/8);
}
function normalizeString(str) {
if (typeof str.normalize == "function") {
return str.normalize("NFKD");
}
else {
// TODO find a library to do this
// Not supported on firefox mobile
console.warn("NFKD Normalization is unavailable");
return str;
}
}
function byteArrayToBinaryString(data) {
var bin = "";
for (var i=0; i<data.length; i++) {
bin += zfill(data[i].toString(2), 8);
}
return bin;
}
function binaryStringToByteArray(str) {
var arrayLen = str.length / 8;
var array = new Uint8Array(arrayLen);
for (var i=0; i<arrayLen; i++) {
var valueStr = str.substring(0,8);
var value = parseInt(valueStr, 2);
array[i] = value;
str = str.slice(8);
}
return array;
}
// Pad a numeric string on the left with zero digits until the given width
// is reached.
// Note this differs to the python implementation because it does not
// handle numbers starting with a sign.
function zfill(source, length) {
source = source.toString();
while (source.length < length) {
source = '0' + source;
}
return source;
}
init();
}
WORDLISTS = {
"english": [
"abandon","ability","able","about","above","absent","absorb","abstract","absurd","abuse",
"access","accident","account","accuse","achieve","acid","acoustic","acquire","across","act",
"action","actor","actress","actual","adapt","add","addict","address","adjust","admit",
"adult","advance","advice","aerobic","affair","afford","afraid","again","age","agent",
"agree","ahead","aim","air","airport","aisle","alarm","album","alcohol","alert",
"alien","all","alley","allow","almost","alone","alpha","already","also","alter",
"always","amateur","amazing","among","amount","amused","analyst","anchor","ancient","anger",
"angle","angry","animal","ankle","announce","annual","another","answer","antenna","antique",
"anxiety","any","apart","apology","appear","apple","approve","april","arch","arctic",
"area","arena","argue","arm","armed","armor","army","around","arrange","arrest",
"arrive","arrow","art","artefact","artist","artwork","ask","aspect","assault","asset",
"assist","assume","asthma","athlete","atom","attack","attend","attitude","attract","auction",
"audit","august","aunt","author","auto","autumn","average","avocado","avoid","awake",
"aware","away","awesome","awful","awkward","axis","baby","bachelor","bacon","badge",
"bag","balance","balcony","ball","bamboo","banana","banner","bar","barely","bargain",
"barrel","base","basic","basket","battle","beach","bean","beauty","because","become",
"beef","before","begin","behave","behind","believe","below","belt","bench","benefit",
"best","betray","better","between","beyond","bicycle","bid","bike","bind","biology",
"bird","birth","bitter","black","blade","blame","blanket","blast","bleak","bless",
"blind","blood","blossom","blouse","blue","blur","blush","board","boat","body",
"boil","bomb","bone","bonus","book","boost","border","boring","borrow","boss",
"bottom","bounce","box","boy","bracket","brain","brand","brass","brave","bread",
"breeze","brick","bridge","brief","bright","bring","brisk","broccoli","broken","bronze",
"broom","brother","brown","brush","bubble","buddy","budget","buffalo","build","bulb",
"bulk","bullet","bundle","bunker","burden","burger","burst","bus","business","busy",
"butter","buyer","buzz","cabbage","cabin","cable","cactus","cage","cake","call",
"calm","camera","camp","can","canal","cancel","candy","cannon","canoe","canvas",
"canyon","capable","capital","captain","car","carbon","card","cargo","carpet","carry",
"cart","case","cash","casino","castle","casual","cat","catalog","catch","category",
"cattle","caught","cause","caution","cave","ceiling","celery","cement","census","century",
"cereal","certain","chair","chalk","champion","change","chaos","chapter","charge","chase",
"chat","cheap","check","cheese","chef","cherry","chest","chicken","chief","child",
"chimney","choice","choose","chronic","chuckle","chunk","churn","cigar","cinnamon","circle",
"citizen","city","civil","claim","clap","clarify","claw","clay","clean","clerk",
"clever","click","client","cliff","climb","clinic","clip","clock","clog","close",
"cloth","cloud","clown","club","clump","cluster","clutch","coach","coast","coconut",
"code","coffee","coil","coin","collect","color","column","combine","come","comfort",
"comic","common","company","concert","conduct","confirm","congress","connect","consider","control",
"convince","cook","cool","copper","copy","coral","core","corn","correct","cost",
"cotton","couch","country","couple","course","cousin","cover","coyote","crack","cradle",
"craft","cram","crane","crash","crater","crawl","crazy","cream","credit","creek",
"crew","cricket","crime","crisp","critic","crop","cross","crouch","crowd","crucial",
"cruel","cruise","crumble","crunch","crush","cry","crystal","cube","culture","cup",
"cupboard","curious","current","curtain","curve","cushion","custom","cute","cycle","dad",
"damage","damp","dance","danger","daring","dash","daughter","dawn","day","deal",
"debate","debris","decade","december","decide","decline","decorate","decrease","deer","defense",
"define","defy","degree","delay","deliver","demand","demise","denial","dentist","deny",
"depart","depend","deposit","depth","deputy","derive","describe","desert","design","desk",
"despair","destroy","detail","detect","develop","device","devote","diagram","dial","diamond",
"diary","dice","diesel","diet","differ","digital","dignity","dilemma","dinner","dinosaur",
"direct","dirt","disagree","discover","disease","dish","dismiss","disorder","display","distance",
"divert","divide","divorce","dizzy","doctor","document","dog","doll","dolphin","domain",
"donate","donkey","donor","door","dose","double","dove","draft","dragon","drama",
"drastic","draw","dream","dress","drift","drill","drink","drip","drive","drop",
"drum","dry","duck","dumb","dune","during","dust","dutch","duty","dwarf",
"dynamic","eager","eagle","early","earn","earth","easily","east","easy","echo",
"ecology","economy","edge","edit","educate","effort","egg","eight","either","elbow",
"elder","electric","elegant","element","elephant","elevator","elite","else","embark","embody",
"embrace","emerge","emotion","employ","empower","empty","enable","enact","end","endless",
"endorse","enemy","energy","enforce","engage","engine","enhance","enjoy","enlist","enough",
"enrich","enroll","ensure","enter","entire","entry","envelope","episode","equal","equip",
"era","erase","erode","erosion","error","erupt","escape","essay","essence","estate",
"eternal","ethics","evidence","evil","evoke","evolve","exact","example","excess","exchange",
"excite","exclude","excuse","execute","exercise","exhaust","exhibit","exile","exist","exit",
"exotic","expand","expect","expire","explain","expose","express","extend","extra","eye",
"eyebrow","fabric","face","faculty","fade","faint","faith","fall","false","fame",
"family","famous","fan","fancy","fantasy","farm","fashion","fat","fatal","father",
"fatigue","fault","favorite","feature","february","federal","fee","feed","feel","female",
"fence","festival","fetch","fever","few","fiber","fiction","field","figure","file",
"film","filter","final","find","fine","finger","finish","fire","firm","first",
"fiscal","fish","fit","fitness","fix","flag","flame","flash","flat","flavor",
"flee","flight","flip","float","flock","floor","flower","fluid","flush","fly",
"foam","focus","fog","foil","fold","follow","food","foot","force","forest",
"forget","fork","fortune","forum","forward","fossil","foster","found","fox","fragile",
"frame","frequent","fresh","friend","fringe","frog","front","frost","frown","frozen",
"fruit","fuel","fun","funny","furnace","fury","future","gadget","gain","galaxy",
"gallery","game","gap","garage","garbage","garden","garlic","garment","gas","gasp",
"gate","gather","gauge","gaze","general","genius","genre","gentle","genuine","gesture",
"ghost","giant","gift","giggle","ginger","giraffe","girl","give","glad","glance",
"glare","glass","glide","glimpse","globe","gloom","glory","glove","glow","glue",
"goat","goddess","gold","good","goose","gorilla","gospel","gossip","govern","gown",
"grab","grace","grain","grant","grape","grass","gravity","great","green","grid",
"grief","grit","grocery","group","grow","grunt","guard","guess","guide","guilt",
"guitar","gun","gym","habit","hair","half","hammer","hamster","hand","happy",
"harbor","hard","harsh","harvest","hat","have","hawk","hazard","head","health",
"heart","heavy","hedgehog","height","hello","helmet","help","hen","hero","hidden",
"high","hill","hint","hip","hire","history","hobby","hockey","hold","hole",
"holiday","hollow","home","honey","hood","hope","horn","horror","horse","hospital",
"host","hotel","hour","hover","hub","huge","human","humble","humor","hundred",
"hungry","hunt","hurdle","hurry","hurt","husband","hybrid","ice","icon","idea",
"identify","idle","ignore","ill","illegal","illness","image","imitate","immense","immune",
"impact","impose","improve","impulse","inch","include","income","increase","index","indicate",
"indoor","industry","infant","inflict","inform","inhale","inherit","initial","inject","injury",
"inmate","inner","innocent","input","inquiry","insane","insect","inside","inspire","install",
"intact","interest","into","invest","invite","involve","iron","island","isolate","issue",
"item","ivory","jacket","jaguar","jar","jazz","jealous","jeans","jelly","jewel",
"job","join","joke","journey","joy","judge","juice","jump","jungle","junior",
"junk","just","kangaroo","keen","keep","ketchup","key","kick","kid","kidney",
"kind","kingdom","kiss","kit","kitchen","kite","kitten","kiwi","knee","knife",
"knock","know","lab","label","labor","ladder","lady","lake","lamp","language",
"laptop","large","later","latin","laugh","laundry","lava","law","lawn","lawsuit",
"layer","lazy","leader","leaf","learn","leave","lecture","left","leg","legal",
"legend","leisure","lemon","lend","length","lens","leopard","lesson","letter","level",
"liar","liberty","library","license","life","lift","light","like","limb","limit",
"link","lion","liquid","list","little","live","lizard","load","loan","lobster",
"local","lock","logic","lonely","long","loop","lottery","loud","lounge","love",
"loyal","lucky","luggage","lumber","lunar","lunch","luxury","lyrics","machine","mad",
"magic","magnet","maid","mail","main","major","make","mammal","man","manage",
"mandate","mango","mansion","manual","maple","marble","march","margin","marine","market",
"marriage","mask","mass","master","match","material","math","matrix","matter","maximum",
"maze","meadow","mean","measure","meat","mechanic","medal","media","melody","melt",
"member","memory","mention","menu","mercy","merge","merit","merry","mesh","message",
"metal","method","middle","midnight","milk","million","mimic","mind","minimum","minor",
"minute","miracle","mirror","misery","miss","mistake","mix","mixed","mixture","mobile",
"model","modify","mom","moment","monitor","monkey","monster","month","moon","moral",
"more","morning","mosquito","mother","motion","motor","mountain","mouse","move","movie",
"much","muffin","mule","multiply","muscle","museum","mushroom","music","must","mutual",
"myself","mystery","myth","naive","name","napkin","narrow","nasty","nation","nature",
"near","neck","need","negative","neglect","neither","nephew","nerve","nest","net",
"network","neutral","never","news","next","nice","night","noble","noise","nominee",
"noodle","normal","north","nose","notable","note","nothing","notice","novel","now",
"nuclear","number","nurse","nut","oak","obey","object","oblige","obscure","observe",
"obtain","obvious","occur","ocean","october","odor","off","offer","office","often",
"oil","okay","old","olive","olympic","omit","once","one","onion","online",
"only","open","opera","opinion","oppose","option","orange","orbit","orchard","order",
"ordinary","organ","orient","original","orphan","ostrich","other","outdoor","outer","output",
"outside","oval","oven","over","own","owner","oxygen","oyster","ozone","pact",
"paddle","page","pair","palace","palm","panda","panel","panic","panther","paper",
"parade","parent","park","parrot","party","pass","patch","path","patient","patrol",
"pattern","pause","pave","payment","peace","peanut","pear","peasant","pelican","pen",
"penalty","pencil","people","pepper","perfect","permit","person","pet","phone","photo",
"phrase","physical","piano","picnic","picture","piece","pig","pigeon","pill","pilot",
"pink","pioneer","pipe","pistol","pitch","pizza","place","planet","plastic","plate",
"play","please","pledge","pluck","plug","plunge","poem","poet","point","polar",
"pole","police","pond","pony","pool","popular","portion","position","possible","post",
"potato","pottery","poverty","powder","power","practice","praise","predict","prefer","prepare",
"present","pretty","prevent","price","pride","primary","print","priority","prison","private",
"prize","problem","process","produce","profit","program","project","promote","proof","property",
"prosper","protect","proud","provide","public","pudding","pull","pulp","pulse","pumpkin",
"punch","pupil","puppy","purchase","purity","purpose","purse","push","put","puzzle",
"pyramid","quality","quantum","quarter","question","quick","quit","quiz","quote","rabbit",
"raccoon","race","rack","radar","radio","rail","rain","raise","rally","ramp",
"ranch","random","range","rapid","rare","rate","rather","raven","raw","razor",
"ready","real","reason","rebel","rebuild","recall","receive","recipe","record","recycle",
"reduce","reflect","reform","refuse","region","regret","regular","reject","relax","release",
"relief","rely","remain","remember","remind","remove","render","renew","rent","reopen",
"repair","repeat","replace","report","require","rescue","resemble","resist","resource","response",
"result","retire","retreat","return","reunion","reveal","review","reward","rhythm","rib",
"ribbon","rice","rich","ride","ridge","rifle","right","rigid","ring","riot",
"ripple","risk","ritual","rival","river","road","roast","robot","robust","rocket",
"romance","roof","rookie","room","rose","rotate","rough","round","route","royal",
"rubber","rude","rug","rule","run","runway","rural","sad","saddle","sadness",
"safe","sail","salad","salmon","salon","salt","salute","same","sample","sand",
"satisfy","satoshi","sauce","sausage","save","say","scale","scan","scare","scatter",
"scene","scheme","school","science","scissors","scorpion","scout","scrap","screen","script",
"scrub","sea","search","season","seat","second","secret","section","security","seed",
"seek","segment","select","sell","seminar","senior","sense","sentence","series","service",
"session","settle","setup","seven","shadow","shaft","shallow","share","shed","shell",
"sheriff","shield","shift","shine","ship","shiver","shock","shoe","shoot","shop",
"short","shoulder","shove","shrimp","shrug","shuffle","shy","sibling","sick","side",
"siege","sight","sign","silent","silk","silly","silver","similar","simple","since",
"sing","siren","sister","situate","six","size","skate","sketch","ski","skill",
"skin","skirt","skull","slab","slam","sleep","slender","slice","slide","slight",
"slim","slogan","slot","slow","slush","small","smart","smile","smoke","smooth",
"snack","snake","snap","sniff","snow","soap","soccer","social","sock","soda",
"soft","solar","soldier","solid","solution","solve","someone","song","soon","sorry",
"sort","soul","sound","soup","source","south","space","spare","spatial","spawn",
"speak","special","speed","spell","spend","sphere","spice","spider","spike","spin",
"spirit","split","spoil","sponsor","spoon","sport","spot","spray","spread","spring",
"spy","square","squeeze","squirrel","stable","stadium","staff","stage","stairs","stamp",
"stand","start","state","stay","steak","steel","stem","step","stereo","stick",
"still","sting","stock","stomach","stone","stool","story","stove","strategy","street",
"strike","strong","struggle","student","stuff","stumble","style","subject","submit","subway",
"success","such","sudden","suffer","sugar","suggest","suit","summer","sun","sunny",
"sunset","super","supply","supreme","sure","surface","surge","surprise","surround","survey",
"suspect","sustain","swallow","swamp","swap","swarm","swear","sweet","swift","swim",
"swing","switch","sword","symbol","symptom","syrup","system","table","tackle","tag",
"tail","talent","talk","tank","tape","target","task","taste","tattoo","taxi",
"teach","team","tell","ten","tenant","tennis","tent","term","test","text",
"thank","that","theme","then","theory","there","they","thing","this","thought",
"three","thrive","throw","thumb","thunder","ticket","tide","tiger","tilt","timber",
"time","tiny","tip","tired","tissue","title","toast","tobacco","today","toddler",
"toe","together","toilet","token","tomato","tomorrow","tone","tongue","tonight","tool",
"tooth","top","topic","topple","torch","tornado","tortoise","toss","total","tourist",
"toward","tower","town","toy","track","trade","traffic","tragic","train","transfer",
"trap","trash","travel","tray","treat","tree","trend","trial","tribe","trick",
"trigger","trim","trip","trophy","trouble","truck","true","truly","trumpet","trust",
"truth","try","tube","tuition","tumble","tuna","tunnel","turkey","turn","turtle",
"twelve","twenty","twice","twin","twist","two","type","typical","ugly","umbrella",
"unable","unaware","uncle","uncover","under","undo","unfair","unfold","unhappy","uniform",
"unique","unit","universe","unknown","unlock","until","unusual","unveil","update","upgrade",
"uphold","upon","upper","upset","urban","urge","usage","use","used","useful",
"useless","usual","utility","vacant","vacuum","vague","valid","valley","valve","van",
"vanish","vapor","various","vast","vault","vehicle","velvet","vendor","venture","venue",
"verb","verify","version","very","vessel","veteran","viable","vibrant","vicious","victory",
"video","view","village","vintage","violin","virtual","virus","visa","visit","visual",
"vital","vivid","vocal","voice","void","volcano","volume","vote","voyage","wage",
"wagon","wait","walk","wall","walnut","want","warfare","warm","warrior","wash",
"wasp","waste","water","wave","way","wealth","weapon","wear","weasel","weather",
"web","wedding","weekend","weird","welcome","west","wet","whale","what","wheat",
"wheel","when","where","whip","whisper","wide","width","wife","wild","will",
"win","window","wine","wing","wink","winner","winter","wire","wisdom","wise",
"wish","witness","wolf","woman","wonder","wood","wool","word","work","world",
"worry","worth","wrap","wreck","wrestle","wrist","write","wrong","yard","year",
"yellow","you","young","youth","zebra","zero","zone","zoo"]
};