X-Git-Url: https://git.immae.eu/?p=perso%2FImmae%2FProjets%2FCryptomonnaies%2FBIP39.git;a=blobdiff_plain;f=tests.js;h=ec368cbf4ec018dbbe4502154a49d13c1b48bc76;hp=914be245532a28eb921bf3e36ec8207b78a60204;hb=c6624d51f4e5607202e48903352574c47571baab;hpb=d737abf6809622228faf7d5fe54101e2d87d72a4 diff --git a/tests.js b/tests.js index 914be24..ec368cb 100644 --- a/tests.js +++ b/tests.js @@ -1960,6 +1960,613 @@ page.open(url, function(status) { }); }, +// Entropy unit tests +function() { +page.open(url, function(status) { + var error = page.evaluate(function() { + var e; + // binary entropy is detected + e = Entropy.fromString("01010101"); + if (e.base.str != "binary") { + return "Binary entropy not detected correctly"; + } + // base6 entropy is detected + e = Entropy.fromString("012345012345"); + if (e.base.str != "base 6") { + return "base6 entropy not detected correctly"; + } + // dice entropy is detected + e = Entropy.fromString("123456123456"); + if (e.base.str != "base 6 (dice)") { + return "dice entropy not detected correctly"; + } + // base10 entropy is detected + e = Entropy.fromString("0123456789"); + if (e.base.str != "base 10") { + return "base10 entropy not detected correctly"; + } + // hex entropy is detected + e = Entropy.fromString("0123456789ABCDEF"); + if (e.base.str != "hexadecimal") { + return "hexadecimal entropy not detected correctly"; + } + // entropy is case insensitive + e = Entropy.fromString("aBcDeF"); + if (e.cleanStr != "aBcDeF") { + return "Entropy should not be case sensitive"; + } + // dice entropy is converted to base6 + e = Entropy.fromString("123456"); + if (e.cleanStr != "012345") { + return "Dice entropy is not automatically converted to base6"; + } + // dice entropy is preferred to base6 if ambiguous + e = Entropy.fromString("12345"); + if (e.base.str != "base 6 (dice)") { + return "dice not used as default over base 6"; + } + // unused characters are ignored + e = Entropy.fromString("fghijkl"); + if (e.cleanStr != "f") { + return "additional characters are not ignored"; + } + // the lowest base is used by default + // 7 could be decimal or hexadecimal, but should be detected as decimal + e = Entropy.fromString("7"); + if (e.base.str != "base 10") { + return "lowest base is not used"; + } + // Hexadecimal representation is returned + e = Entropy.fromString("1010"); + if (e.hexStr != "A") { + return "Hexadecimal representation not returned"; + } + // Leading zeros are retained + e = Entropy.fromString("000A"); + if (e.cleanStr != "000A") { + return "Leading zeros are not retained"; + } + // Leading zeros are correctly preserved for hex in binary string + e = Entropy.fromString("2A"); + if (e.binaryStr != "00101010") { + return "Hex leading zeros are not correct in binary"; + } + // Keyboard mashing results in weak entropy + // Despite being a long string, it's less than 30 bits of entropy + e = Entropy.fromString("aj;se ifj; ask,dfv js;ifj"); + if (e.binaryStr.length >= 30) { + return "Keyboard mashing should produce weak entropy"; + } + return false; + }); + if (error) { + console.log("Entropy unit tests"); + console.log(error); + fail(); + }; + next(); +}); +}, + +// Entropy can be entered by the user +function() { +page.open(url, function(status) { + expected = { + mnemonic: "abandon abandon ability", + address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug", + } + // use entropy + page.evaluate(function() { + $(".use-entropy").prop("checked", true).trigger("change"); + $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input"); + }); + // check the mnemonic is set and address is correct + waitForGenerate(function() { + var actual = page.evaluate(function() { + return { + address: $(".address:first").text(), + mnemonic: $(".phrase").val(), + } + }); + if (actual.mnemonic != expected.mnemonic) { + console.log("Entropy does not generate correct mnemonic"); + console.log("Expected: " + expected.mnemonic); + console.log("Got: " + actual.mnemonic); + fail(); + } + if (actual.address != expected.address) { + console.log("Entropy does not generate correct address"); + console.log("Expected: " + expected.address); + console.log("Got: " + actual.address); + fail(); + } + next(); + }); +}); +}, + +// A warning about entropy is shown to the user, with additional information +function() { +page.open(url, function(status) { + // get text content from entropy sections of page + var hasWarning = page.evaluate(function() { + var entropyText = $(".entropy-container").text(); + var warning = "mnemonic may be insecure"; + if (entropyText.indexOf(warning) == -1) { + return false; + } + var readMoreText = $("#entropy-notes").parent().text(); + var goodSources = "flipping a fair coin, rolling a fair dice, noise measurements etc"; + if (readMoreText.indexOf(goodSources) == -1) { + return false; + } + return true; + }); + // check the warnings and information are shown + if (!hasWarning) { + console.log("Page does not contain warning about using own entropy"); + fail(); + } + next(); +}); +}, + +// The types of entropy available are described to the user +function() { +page.open(url, function(status) { + // get placeholder text for entropy field + var placeholder = page.evaluate(function() { + return $(".entropy").attr("placeholder"); + }); + var options = [ + "binary", + "base 6", + "dice", + "base 10", + "hexadecimal", + ]; + for (var i=0; i -1) { + console.log("Dice entropy value is shown instead of true base 6 value"); + fail(); + } + next(); + }); +}); +}, + +// The number of bits of entropy accumulated is shown +function() { +page.open(url, function(status) { + var tests = { + "0000 0000 0000 0000 0000": "20", + "0": "1", + "0000": "4", + "6": "3", + "7": "3", + "8": "4", + "F": "4", + "29": "5", + "0A": "8", + "1A": "8", // hex is always multiple of 4 bits of entropy + "2A": "8", + "4A": "8", + "8A": "8", + "FA": "8", + "000A": "16", + "2220": "10", + "2221": "9", // uses dice, so entropy is actually 1110 + "2227": "12", + "222F": "16", + "FFFF": "16", + } + // Arrange tests in array so last one can be easily detected + var entropys = []; + var results = []; + for (var entropy in tests) { + entropys.push(entropy); + results.push(tests[entropy]); + } + // use entropy + page.evaluate(function(e) { + $(".use-entropy").prop("checked", true).trigger("change"); + }); + // Run each test + var nextTest = function runNextTest(i) { + var entropy = entropys[i]; + var expected = results[i]; + // set entropy + page.evaluate(function(e) { + $(".addresses").empty(); // bit of a hack, but needed for waitForGenerate + $(".entropy").val(e).trigger("input"); + }, entropy); + // check the number of bits of entropy is shown + waitForGenerate(function() { + var entropyText = page.evaluate(function() { + return $(".entropy-container").text(); + }); + if (entropyText.indexOf("Have " + expected + " bits of entropy") == -1) { + console.log("Accumulated entropy is not shown correctly for " + entropy); + fail(); + } + var isLastTest = i == results.length - 1; + if (isLastTest) { + next(); + } + else { + runNextTest(i+1); + } + }); + } + nextTest(0); +}); +}, + +// The number of bits of entropy to reach the next mnemonic strength is shown +function() { +page.open(url, function(status) { + // use entropy + page.evaluate(function() { + $(".use-entropy").prop("checked", true).trigger("change"); + $(".entropy").val("7654321").trigger("input"); + }); + // check the amount of additional entropy required is shown + waitForGenerate(function() { + var entropyText = page.evaluate(function() { + return $(".entropy-container").text(); + }); + if (entropyText.indexOf("3 more base 10 chars required") == -1) { + console.log("Additional entropy requirement is not shown"); + fail(); + } + next(); + }); +}); +}, + +// The next strength above 0-word mnemonics is considered extremely weak +// The next strength above 3-word mnemonics is considered very weak +// The next strength above 6-word mnemonics is considered weak +// The next strength above 9-word mnemonics is considered strong +// The next strength above 12-word mnemonics is considered very strong +// The next strength above 15-word mnemonics is considered extremely strong +function() { +page.open(url, function(status) { + var tests = [ + { + entropy: "A", + words: 0, + nextStrength: "an extremely weak", + }, + { + entropy: "AAAAAAAA", + words: 3, + nextStrength: "a very weak", + }, + { + entropy: "AAAAAAAA B", + words: 3, + nextStrength: "a very weak", + }, + { + entropy: "AAAAAAAA BBBBBBBB", + words: 6, + nextStrength: "a weak", + }, + { + entropy: "AAAAAAAA BBBBBBBB CCCCCCCC", + words: 9, + nextStrength: "a strong", + }, + { + entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD", + words: 12, + nextStrength: "a very strong", + }, + { + entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD EEEEEEEE", + words: 15, + nextStrength: "an extremely strong", + } + ]; + // use entropy + page.evaluate(function() { + $(".use-entropy").prop("checked", true).trigger("change"); + }); + var nextTest = function runNextTest(i) { + test = tests[i]; + page.evaluate(function(e) { + $(".addresses").empty(); + $(".entropy").val(e).trigger("input"); + }, test.entropy); + waitForGenerate(function() { + // check the strength of the current mnemonic + var mnemonic = page.evaluate(function() { + return $(".phrase").val(); + }); + if (test.words == 0) { + if (mnemonic.length > 0) { + console.log("Mnemonic length for " + test.nextStrength + " strength is not " + test.words); + console.log("Mnemonic: " + mnemonic); + fail(); + } + } + else { + if (mnemonic.split(" ").length != test.words) { + console.log("Mnemonic length for " + test.nextStrength + " strength is not " + test.words); + console.log("Mnemonic: " + mnemonic); + fail(); + } + } + // check the strength of the next mnemonic is shown + var entropyText = page.evaluate(function() { + return $(".entropy-container").text(); + }); + if (entropyText.indexOf("required to generate " + test.nextStrength + " mnemonic") == -1) { + console.log("Strength indicator for " + test.nextStrength + " mnemonic is incorrect"); + fail(); + } + var isLastTest = i == tests.length - 1; + if (isLastTest) { + next(); + } + else { + runNextTest(i+1); + } + }); + } + nextTest(0); +}); +}, + +// Entropy is truncated from the right +function() { +page.open(url, function(status) { + var expected = "abandon abandon ability"; + // use entropy + page.evaluate(function() { + $(".use-entropy").prop("checked", true).trigger("change"); + var entropy = "00000000 00000000 00000000 00000000"; + entropy += "11111111 11111111 11111111 1111"; // Missing last byte, only first 8 bytes are used + $(".entropy").val(entropy).trigger("input"); + }); + // check the entropy is truncated from the right + waitForGenerate(function() { + var actual = page.evaluate(function() { + return $(".phrase").val(); + }); + if (actual != expected) { + console.log("Entropy is not truncated from the right"); + console.log("Expected: " + expected); + console.log("Got: " + actual); + fail(); + } + next(); + }); +}); +}, + +// Very large entropy results in very long mnemonics +function() { +page.open(url, function(status) { + // use entropy + page.evaluate(function() { + $(".use-entropy").prop("checked", true).trigger("change"); + var entropy = ""; + // Generate a very long entropy string + for (var i=0; i<33; i++) { + entropy += "AAAAAAAA"; // 3 words * 33 iterations = 99 words + } + $(".entropy").val(entropy).trigger("input"); + }); + // check the mnemonic is very long + waitForGenerate(function() { + var wordCount = page.evaluate(function() { + return $(".phrase").val().split(" ").length; + }); + if (wordCount != 99) { + console.log("Large entropy does not generate long mnemonic"); + console.log("Expected 99 words, got " + wordCount); + fail(); + } + next(); + }); +}); +}, + +// Is compatible with bip32jp entropy +// https://bip32jp.github.io/english/index.html +// NOTES: +// Is incompatible with: +// base 6 with leading zeros +// base 6 wth 12 words / 53 chars +// base 20 +function() { +page.open(url, function(status) { + var expected = "defy trip fatal jaguar mean rack rifle survey satisfy drift twist champion steel wife state furnace night consider glove olympic oblige donor novel left"; + // use entropy + page.evaluate(function() { + $(".use-entropy").prop("checked", true).trigger("change"); + var entropy = "123450123450123450123450123450123450123450123450123450123450123450123450123450123450123450123450123"; + $(".entropy").val(entropy).trigger("input"); + }); + // check the mnemonic matches the expected value from bip32jp + waitForGenerate(function() { + var actual = page.evaluate(function() { + return $(".phrase").val(); + }); + if (actual != expected) { + console.log("Mnemonic does not match bip32jp for base 6 entropy"); + console.log("Expected: " + expected); + console.log("Got: " + actual); + fail(); + } + next(); + }); +}); +}, + // If you wish to add more tests, do so here... // Here is a blank test template