X-Git-Url: https://git.immae.eu/?p=perso%2FImmae%2FProjets%2FCryptomonnaies%2FBIP39.git;a=blobdiff_plain;f=src%2Fjs%2Findex.js;h=fc467fe55188f9428dbec3d57377e430a07afc4c;hp=edd70c534e9ab0c85bda0b0b88a3d4220b3e20bc;hb=e9491c7efd88b23fc505f546845f52bfbb7fd864;hpb=96ee8ab0a0e589c1fab745953909a031f79a3873 diff --git a/src/js/index.js b/src/js/index.js index edd70c5..fc467fe 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -6,7 +6,7 @@ var seed = null; var bip32RootKey = null; var bip32ExtendedKey = null; - var network = bitcoinjs.bitcoin.networks.bitcoin; + var network = libs.bitcoin.networks.bitcoin; var addressRowTemplate = $("#address-row-template"); var showIndex = true; @@ -15,10 +15,11 @@ var showPrivKey = true; var showQr = false; var litecoinUseLtub = true; - var isDefaultBip44ChangeValue = true; + var entropyTypeAutoDetect = true; var entropyChangeTimeoutEvent = null; var phraseChangeTimeoutEvent = null; + var seedChangedTimeoutEvent = null; var rootKeyChangedTimeoutEvent = null; var generationProcesses = []; @@ -33,6 +34,7 @@ DOM.entropy = $(".entropy"); DOM.entropyFiltered = DOM.entropyContainer.find(".filtered"); DOM.entropyType = DOM.entropyContainer.find(".type"); + DOM.entropyTypeInputs = DOM.entropyContainer.find("input[name='entropy-type']"); DOM.entropyCrackTime = DOM.entropyContainer.find(".crack-time"); DOM.entropyEventCount = DOM.entropyContainer.find(".event-count"); DOM.entropyBits = DOM.entropyContainer.find(".bits"); @@ -45,7 +47,7 @@ DOM.entropyWeakEntropyOverrideWarning = DOM.entropyContainer.find(".weak-entropy-override-warning"); DOM.entropyFilterWarning = DOM.entropyContainer.find(".filter-warning"); DOM.phrase = $(".phrase"); - DOM.splitPhrase = $(".phraseSplit"); + DOM.phraseSplit = $(".phraseSplit"); DOM.phraseSplitWarn = $(".phraseSplitWarn"); DOM.passphrase = $(".passphrase"); DOM.generateContainer = $(".generate-container"); @@ -72,7 +74,6 @@ DOM.bip44accountXprv = $("#bip44 .account-xprv"); DOM.bip44accountXpub = $("#bip44 .account-xpub"); DOM.bip44change = $("#bip44 .change"); - DOM.defaultBip44ChangeValue = $("#bip44 .default-bip44-change-value"); DOM.bip49unavailable = $("#bip49 .unavailable"); DOM.bip49available = $("#bip49 .available"); DOM.bip49path = $("#bip49-path"); @@ -130,17 +131,17 @@ DOM.useEntropy.on("change", setEntropyVisibility); DOM.entropy.on("input", delayedEntropyChanged); DOM.entropyMnemonicLength.on("change", entropyChanged); + DOM.entropyTypeInputs.on("change", entropyTypeChanged); DOM.phrase.on("input", delayedPhraseChanged); DOM.passphrase.on("input", delayedPhraseChanged); DOM.generate.on("click", generateClicked); DOM.more.on("click", showMore); + DOM.seed.on("input", delayedSeedChanged); DOM.rootKey.on("input", delayedRootKeyChanged); DOM.litecoinUseLtub.on("change", litecoinUseLtubChanged); DOM.bip32path.on("input", calcForDerivationPath); DOM.bip44account.on("input", calcForDerivationPath); - DOM.bip44change.on("input", modifiedDefaultBip44ChangeValue); DOM.bip44change.on("input", calcForDerivationPath); - DOM.defaultBip44ChangeValue.on("click", resetDefaultBip44ChangeValue); DOM.bip49account.on("input", calcForDerivationPath); DOM.bip49change.on("input", calcForDerivationPath); DOM.bip84account.on("input", calcForDerivationPath); @@ -244,6 +245,8 @@ if (entropy !== null) { DOM.entropyMnemonicLength.val("raw"); DOM.entropy.val(entropy); + DOM.entropyTypeInputs.filter("[value='hexadecimal']").prop("checked", true); + entropyTypeAutoDetect = false; } }, 400); } @@ -264,6 +267,7 @@ calcForDerivationPath(); // Show the word indexes showWordIndexes(); + writeSplitPhrase(phrase); } function tabChanged() { @@ -333,6 +337,35 @@ } } + function entropyTypeChanged() { + entropyTypeAutoDetect = false; + entropyChanged(); + } + + function delayedSeedChanged() { + // Warn if there is an existing mnemonic or passphrase. + if (DOM.phrase.val().length > 0 || DOM.passphrase.val().length > 0) { + if (!confirm("This will clear existing mnemonic and passphrase")) { + DOM.seed.val(seed); + return + } + } + hideValidationError(); + showPending(); + // Clear existing mnemonic and passphrase + DOM.phrase.val(""); + DOM.phraseSplit.val(""); + DOM.passphrase.val(""); + DOM.rootKey.val(""); + clearAddressesList(); + clearDerivedKeys(); + seed = null; + if (seedChangedTimeoutEvent != null) { + clearTimeout(seedChangedTimeoutEvent); + } + seedChangedTimeoutEvent = setTimeout(seedChanged, 400); + } + function delayedRootKeyChanged() { // Warn if there is an existing mnemonic or passphrase. if (DOM.phrase.val().length > 0 || DOM.passphrase.val().length > 0) { @@ -354,6 +387,22 @@ rootKeyChangedTimeoutEvent = setTimeout(rootKeyChanged, 400); } + function seedChanged() { + showPending(); + hideValidationError(); + seed = DOM.seed.val(); + bip32RootKey = libs.bitcoin.HDNode.fromSeedHex(seed, network); + var rootKeyBase58 = bip32RootKey.toBase58(); + DOM.rootKey.val(rootKeyBase58); + var errorText = validateRootKey(rootKeyBase58); + if (errorText) { + showValidationError(errorText); + return; + } + // Calculate and display + calcForDerivationPath(); + } + function rootKeyChanged() { showPending(); hideValidationError(); @@ -371,10 +420,10 @@ function litecoinUseLtubChanged() { litecoinUseLtub = DOM.litecoinUseLtub.prop("checked"); if (litecoinUseLtub) { - network = bitcoinjs.bitcoin.networks.litecoin; + network = libs.bitcoin.networks.litecoin; } else { - network = bitcoinjs.bitcoin.networks.litecoinXprv; + network = libs.bitcoin.networks.litecoinXprv; } phraseChanged(); } @@ -432,7 +481,6 @@ if (DOM.phrase.val().length > 0) { var newPhrase = convertPhraseToNewLanguage(); DOM.phrase.val(newPhrase); - writeSplitPhrase(newPhrase); phraseChanged(); } else { @@ -493,7 +541,6 @@ // show the words var words = mnemonic.toMnemonic(data); DOM.phrase.val(words); - writeSplitPhrase(words); // show the entropy var entropyHex = uint8ArrayToHex(data); DOM.entropy.val(entropyHex); @@ -504,9 +551,9 @@ function calcBip32RootKeyFromSeed(phrase, passphrase) { seed = mnemonic.toSeed(phrase, passphrase); - bip32RootKey = bitcoinjs.bitcoin.HDNode.fromSeedHex(seed, network); + bip32RootKey = libs.bitcoin.HDNode.fromSeedHex(seed, network); if(isGRS()) - bip32RootKey = groestlcoinjs.HDNode.fromSeedHex(seed, network); + bip32RootKey = libs.groestlcoinjs.HDNode.fromSeedHex(seed, network); } @@ -520,18 +567,18 @@ if (networkHasSegwit()) { var n = network; if ("baseNetwork" in n) { - n = bitcoinjs.bitcoin.networks[n.baseNetwork]; + n = libs.bitcoin.networks[n.baseNetwork]; } // try parsing using base network params try { - bip32RootKey = bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n); + bip32RootKey = libs.bitcoin.HDNode.fromBase58(rootKeyBase58, n); return; } catch (e) {} // try parsing using p2wpkh params if ("p2wpkh" in n) { try { - bip32RootKey = bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkh); + bip32RootKey = libs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkh); return; } catch (e) {} @@ -539,14 +586,30 @@ // try parsing using p2wpkh-in-p2sh network params if ("p2wpkhInP2sh" in n) { try { - bip32RootKey = bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkhInP2sh); + bip32RootKey = libs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkhInP2sh); + return; + } + catch (e) {} + } + // try parsing using p2wsh network params + if ("p2wsh" in n) { + try { + bip32RootKey = libs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wsh); + return; + } + catch (e) {} + } + // try parsing using p2wsh-in-p2sh network params + if ("p2wshInP2sh" in n) { + try { + bip32RootKey = libs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wshInP2sh); return; } catch (e) {} } } // try the network params as currently specified - bip32RootKey = bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, network); + bip32RootKey = libs.bitcoin.HDNode.fromBase58(rootKeyBase58, network); } function calcBip32RootKeyFromBase58GRS(rootKeyBase58) { @@ -555,18 +618,18 @@ if (networkHasSegwit()) { var n = network; if ("baseNetwork" in n) { - n = bitcoinjs.bitcoin.networks[n.baseNetwork]; + n = libs.bitcoin.networks[n.baseNetwork]; } // try parsing using base network params try { - bip32RootKey = groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n); + bip32RootKey = libs.groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n); return; } catch (e) {} // try parsing using p2wpkh params if ("p2wpkh" in n) { try { - bip32RootKey = groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n.p2wpkh); + bip32RootKey = libs.groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n.p2wpkh); return; } catch (e) {} @@ -574,14 +637,14 @@ // try parsing using p2wpkh-in-p2sh network params if ("p2wpkhInP2sh" in n) { try { - bip32RootKey = groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n.p2wpkhInP2sh); + bip32RootKey = libs.groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n.p2wpkhInP2sh); return; } catch (e) {} } } // try the network params as currently specified - bip32RootKey = groestlcoinjs.HDNode.fromBase58(rootKeyBase58, network); + bip32RootKey = libs.groestlcoinjs.HDNode.fromBase58(rootKeyBase58, network); } function calcBip32ExtendedKey(path) { @@ -662,18 +725,18 @@ if (networkHasSegwit()) { var n = network; if ("baseNetwork" in n) { - n = bitcoinjs.bitcoin.networks[n.baseNetwork]; + n = libs.bitcoin.networks[n.baseNetwork]; } // try parsing using base network params try { - bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n); + libs.bitcoin.HDNode.fromBase58(rootKeyBase58, n); return ""; } catch (e) {} // try parsing using p2wpkh params if ("p2wpkh" in n) { try { - bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkh); + libs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkh); return ""; } catch (e) {} @@ -681,7 +744,23 @@ // try parsing using p2wpkh-in-p2sh network params if ("p2wpkhInP2sh" in n) { try { - bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkhInP2sh); + libs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wpkhInP2sh); + return ""; + } + catch (e) {} + } + // try parsing using p2wsh network params + if ("p2wsh" in n) { + try { + libs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wsh); + return ""; + } + catch (e) {} + } + // try parsing using p2wsh-in-p2sh network params + if ("p2wshInP2sh" in n) { + try { + libs.bitcoin.HDNode.fromBase58(rootKeyBase58, n.p2wshInP2sh); return ""; } catch (e) {} @@ -689,7 +768,7 @@ } // try the network params as currently specified try { - bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, network); + libs.bitcoin.HDNode.fromBase58(rootKeyBase58, network); } catch (e) { return "Invalid root key"; @@ -703,18 +782,18 @@ if (networkHasSegwit()) { var n = network; if ("baseNetwork" in n) { - n = bitcoinjs.bitcoin.networks[n.baseNetwork]; + n = libs.bitcoin.networks[n.baseNetwork]; } // try parsing using base network params try { - groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n); + libs.groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n); return ""; } catch (e) {} // try parsing using p2wpkh params if ("p2wpkh" in n) { try { - groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n.p2wpkh); + libs.groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n.p2wpkh); return ""; } catch (e) {} @@ -722,7 +801,7 @@ // try parsing using p2wpkh-in-p2sh network params if ("p2wpkhInP2sh" in n) { try { - groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n.p2wpkhInP2sh); + libs.groestlcoinjs.HDNode.fromBase58(rootKeyBase58, n.p2wpkhInP2sh); return ""; } catch (e) {} @@ -730,7 +809,7 @@ } // try the network params as currently specified try { - groestlcoinjs.HDNode.fromBase58(rootKeyBase58, network); + libs.groestlcoinjs.HDNode.fromBase58(rootKeyBase58, network); } catch (e) { return "Invalid root key"; @@ -743,14 +822,12 @@ 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(), ""); - var path = "m"; - path += "/" + purpose + "'"; - path += "/" + coin + "'"; - path += "/" + account + "'"; - if (change !== "") { - path += "/" + change; - } + var change = parseIntNoNaN(DOM.bip44change.val(), 0); + var path = "m/"; + path += purpose + "'/"; + path += coin + "'/"; + path += account + "'/"; + path += change; DOM.bip44path.val(path); var derivationPath = DOM.bip44path.val(); console.log("Using derivation path from BIP44 tab: " + derivationPath); @@ -975,6 +1052,14 @@ (bip141TabSelected() && DOM.bip141semantics.val() == "p2wpkh-p2sh"); } + function p2wshSelected() { + return bip141TabSelected() && DOM.bip141semantics.val() == "p2wsh"; + } + + function p2wshInP2shSelected() { + return (bip141TabSelected() && DOM.bip141semantics.val() == "p2wsh-p2sh"); + } + function TableRow(index, isLast) { var self = this; @@ -986,6 +1071,8 @@ var segwitAvailable = networkHasSegwit(); var isP2wpkh = p2wpkhSelected(); var isP2wpkhInP2sh = p2wpkhInP2shSelected(); + var isP2wsh = p2wshSelected(); + var isP2wshInP2sh = p2wshInP2shSelected(); function init() { calculateValues(); @@ -1009,9 +1096,9 @@ var keyPair = key.keyPair; var useUncompressed = useBip38; if (useUncompressed) { - keyPair = new bitcoinjs.bitcoin.ECPair(keyPair.d, null, { network: network, compressed: false }); + keyPair = new libs.bitcoin.ECPair(keyPair.d, null, { network: network, compressed: false }); if(isGRS()) - keyPair = new groestlcoinjs.ECPair(keyPair.d, null, { network: network, compressed: false }); + keyPair = new libs.groestlcoinjs.ECPair(keyPair.d, null, { network: network, compressed: false }); } // get address @@ -1024,11 +1111,11 @@ // BIP38 encode private key if required if (useBip38) { if(isGRS()) - privkey = groestlcoinjsBip38.encrypt(keyPair.d.toBuffer(), false, bip38password, function(p) { + privkey = libs.groestlcoinjsBip38.encrypt(keyPair.d.toBuffer(), false, bip38password, function(p) { console.log("Progressed " + p.percent.toFixed(1) + "% for index " + index); }, null, networks[DOM.network.val()].name.includes("Testnet")); else - privkey = bitcoinjsBip38.encrypt(keyPair.d.toBuffer(), false, bip38password, function(p) { + privkey = libs.bip38.encrypt(keyPair.d.toBuffer(), false, bip38password, function(p) { console.log("Progressed " + p.percent.toFixed(1) + "% for index " + index); }); } @@ -1041,14 +1128,14 @@ } // Ethereum values are different if (networkIsEthereum()) { - var privKeyBuffer = keyPair.d.toBuffer(32); - privkey = privKeyBuffer.toString('hex'); - var addressBuffer = ethUtil.privateToAddress(privKeyBuffer); + var pubkeyBuffer = keyPair.getPublicKeyBuffer(); + var ethPubkey = libs.ethUtil.importPublic(pubkeyBuffer); + var addressBuffer = libs.ethUtil.publicToAddress(ethPubkey); var hexAddress = addressBuffer.toString('hex'); - var checksumAddress = ethUtil.toChecksumAddress(hexAddress); - address = ethUtil.addHexPrefix(checksumAddress); - privkey = ethUtil.addHexPrefix(privkey); - pubkey = ethUtil.addHexPrefix(pubkey); + var checksumAddress = libs.ethUtil.toChecksumAddress(hexAddress); + address = libs.ethUtil.addHexPrefix(checksumAddress); + privkey = libs.ethUtil.addHexPrefix(privkey); + pubkey = libs.ethUtil.addHexPrefix(pubkey); } // Stellar is different @@ -1058,15 +1145,14 @@ var path = "m/"; path += purpose + "'/"; path += coin + "'/" + index + "'"; - var keypair = stellarUtil.getKeypair(path, seed); + var keypair = libs.stellarUtil.getKeypair(path, seed); indexText = path; privkey = keypair.secret(); pubkey = address = keypair.publicKey(); } if ((networks[DOM.network.val()].name == "NAS - Nebulas")) { - var NasAccount = require("nebulas-account"); var privKeyBuffer = keyPair.d.toBuffer(32); - var nebulasAccount = new NasAccount(); + var nebulasAccount = libs.nebulas.Account.NewAccount(); nebulasAccount.setPrivateKey(privKeyBuffer); address = nebulasAccount.getAddressString(); privkey = nebulasAccount.getPrivateKeyString(); @@ -1086,17 +1172,17 @@ if (networks[DOM.network.val()].name == "BCH - Bitcoin Cash") { var bchAddrType = DOM.bitcoinCashAddressType.filter(":checked").val(); if (bchAddrType == "cashaddr") { - address = bchaddr.toCashAddress(address); + address = libs.bchaddr.toCashAddress(address); } else if (bchAddrType == "bitpay") { - address = bchaddr.toBitpayAddress(address); + address = libs.bchaddr.toBitpayAddress(address); } } // Bitcoin Cash address format may vary if (networks[DOM.network.val()].name == "SLP - Simple Ledger Protocol") { var bchAddrType = DOM.bitcoinCashAddressType.filter(":checked").val(); if (bchAddrType == "cashaddr") { - address = bchaddr.toSlpAddress(address); + address = libs.bchaddrSlp.toSlpAddress(address); } } // Segwit addresses are different @@ -1105,21 +1191,36 @@ return; } if (isP2wpkh) { - var keyhash = bitcoinjs.bitcoin.crypto.hash160(key.getPublicKeyBuffer()); - var scriptpubkey = bitcoinjs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash); - address = bitcoinjs.bitcoin.address.fromOutputScript(scriptpubkey, network) + var keyhash = libs.bitcoin.crypto.hash160(key.getPublicKeyBuffer()); + var scriptpubkey = libs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash); + address = libs.bitcoin.address.fromOutputScript(scriptpubkey, network) } else if (isP2wpkhInP2sh) { - var keyhash = bitcoinjs.bitcoin.crypto.hash160(key.getPublicKeyBuffer()); - var scriptsig = bitcoinjs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash); - var addressbytes = bitcoinjs.bitcoin.crypto.hash160(scriptsig); - var scriptpubkey = bitcoinjs.bitcoin.script.scriptHash.output.encode(addressbytes); - address = bitcoinjs.bitcoin.address.fromOutputScript(scriptpubkey, network) + var keyhash = libs.bitcoin.crypto.hash160(key.getPublicKeyBuffer()); + var scriptsig = libs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash); + var addressbytes = libs.bitcoin.crypto.hash160(scriptsig); + var scriptpubkey = libs.bitcoin.script.scriptHash.output.encode(addressbytes); + address = libs.bitcoin.address.fromOutputScript(scriptpubkey, network) + } + else if (isP2wsh) { + // https://github.com/libs.bitcoinjs-lib/blob/v3.3.2/test/integration/addresses.js#L71 + // This is a 1-of-1 + var witnessScript = libs.bitcoin.script.multisig.output.encode(1, [key.getPublicKeyBuffer()]); + var scriptPubKey = libs.bitcoin.script.witnessScriptHash.output.encode(libs.bitcoin.crypto.sha256(witnessScript)); + address = libs.bitcoin.address.fromOutputScript(scriptPubKey, network); + } + else if (isP2wshInP2sh) { + // https://github.com/libs.bitcoinjs-lib/blob/v3.3.2/test/integration/transactions.js#L183 + // This is a 1-of-1 + var witnessScript = libs.bitcoin.script.multisig.output.encode(1, [key.getPublicKeyBuffer()]); + var redeemScript = libs.bitcoin.script.witnessScriptHash.output.encode(libs.bitcoin.crypto.sha256(witnessScript)); + var scriptPubKey = libs.bitcoin.script.scriptHash.output.encode(libs.bitcoin.crypto.hash160(redeemScript)); + address = libs.bitcoin.address.fromOutputScript(scriptPubKey, network) } } if ((networks[DOM.network.val()].name == "CRW - Crown")) { - address = bitcoinjs.bitcoin.networks.crown.toNewAddress(address); + address = libs.bitcoin.networks.crown.toNewAddress(address); } if (networks[DOM.network.val()].name == "EOS - EOSIO") { @@ -1136,10 +1237,10 @@ return; } if (isP2wpkh) { - address = groestlcoinjs.address.fromOutputScript(scriptpubkey, network) + address = libs.groestlcoinjs.address.fromOutputScript(scriptpubkey, network) } else if (isP2wpkhInP2sh) { - address = groestlcoinjs.address.fromOutputScript(scriptpubkey, network) + address = libs.groestlcoinjs.address.fromOutputScript(scriptpubkey, network) } } //non-segwit addresses are handled by using groestlcoinjs for bip32RootKey @@ -1293,7 +1394,7 @@ if (comparedTo.indexOf(word) == 0) { return comparedTo; } - var distance = Levenshtein.get(word, comparedTo); + var distance = libs.levenshtein.get(word, comparedTo); if (distance < minDistance) { closestWord = comparedTo; minDistance = distance; @@ -1450,37 +1551,46 @@ } function writeSplitPhrase(phrase) { - var wordCount = phrase.split(/\s/g).length; //get number of words in phrase - var left=[]; //initialize array of indexs - for (var i=0;i0) { //while indexs left - groupI=(groupI+1)%3; //get next group to insert index into - seed = seed * 16807 % 2147483647; //change random value.(simple predicatable random number generator works well for this use) - var selected=Math.floor(left.length*(seed - 1) / 2147483646); //get index in left we will use for this group - group[groupI].push(left[selected]); //add index to group - left.splice(selected,1); //remove selected index - } - var cards=[phrase.split(/\s/g),phrase.split(/\s/g),phrase.split(/\s/g)];//make array of cards - for (var i=0;i<3;i++) { //go through each card - for (var ii=0;ii0) { + groupI=(groupI+1)%3; + seed = seed * 16807 % 2147483647; + var selected=Math.floor(left.length*(seed - 1) / 2147483646); + group[groupI].push(left[selected]); + left.splice(selected,1); + } + var cards=[phrase.split(/\s/g),phrase.split(/\s/g),phrase.split(/\s/g)]; + for (var i=0;i<3;i++) { + for (var ii=0;ii