From dc6179dcd00e7f420a89f2c001e55d7796d01e18 Mon Sep 17 00:00:00 2001 From: Benjamin Dweck Date: Fri, 12 Mar 2021 14:32:04 +0200 Subject: [PATCH] Refactored InteractiveBinaryEntropyGenerator code BUGFIX: Implemented non-interactive entropy for D8 and Binary --- .../java/io/rudefox/burrow/EventBuffer.java | 14 +- .../InteractiveBinaryEntropyGenerator.java | 58 +++++ .../InteractiveDice8EntropyGenerator.java | 60 ++++++ .../burrow/InteractiveEntropyGenerator.java | 198 +++++++----------- .../io/rudefox/burrow/MnemonicCommand.java | 33 +-- .../burrow/mnemonic_command_tests.java | 24 +++ 6 files changed, 249 insertions(+), 138 deletions(-) create mode 100644 src/main/java/io/rudefox/burrow/InteractiveBinaryEntropyGenerator.java create mode 100644 src/main/java/io/rudefox/burrow/InteractiveDice8EntropyGenerator.java diff --git a/src/main/java/io/rudefox/burrow/EventBuffer.java b/src/main/java/io/rudefox/burrow/EventBuffer.java index 13512f7..bc03b9f 100644 --- a/src/main/java/io/rudefox/burrow/EventBuffer.java +++ b/src/main/java/io/rudefox/burrow/EventBuffer.java @@ -20,12 +20,24 @@ class EventBuffer { return (int) Math.ceil(this.targetBitsOfEntropy * Math.log(2) / Math.log(eventBase)); } - void appendEvents(String eventString) { + void appendBinaryEvents(String eventString) { + for (char inChar : eventString.toCharArray()) + if (inChar >= '0' && inChar <= '1' && events() < getRequiredEvents()) + append(inChar); + } + + void appendD6Events(String eventString) { for (char inChar : eventString.toCharArray()) if (inChar >= '1' && inChar <= '6' && events() < getRequiredEvents()) append((char) (inChar - 1)); } + void appendD8Events(String eventString) { + for (char inChar : eventString.toCharArray()) + if (inChar >= '1' && inChar <= '8' && events() < getRequiredEvents()) + append((char) (inChar - 1)); + } + Entropy toEntropy() { UnsignedInt entropy = new UnsignedInt(toString(), eventBase); byte[] entropyBytes = entropy.getLowestOrderBits(this.targetBitsOfEntropy).toBigEndianByteArray(); diff --git a/src/main/java/io/rudefox/burrow/InteractiveBinaryEntropyGenerator.java b/src/main/java/io/rudefox/burrow/InteractiveBinaryEntropyGenerator.java new file mode 100644 index 0000000..38bcb06 --- /dev/null +++ b/src/main/java/io/rudefox/burrow/InteractiveBinaryEntropyGenerator.java @@ -0,0 +1,58 @@ +package io.rudefox.burrow; + +import java.util.Scanner; +import java.util.regex.Pattern; + +public class InteractiveBinaryEntropyGenerator extends InteractiveEntropyGenerator { + + public InteractiveBinaryEntropyGenerator(int targetBitsOfEntropy, Scanner inputScanner, boolean ansiColorOutput) { + super(inputScanner, ansiColorOutput, targetBitsOfEntropy, 1); + } + + @Override + protected String readNextEventSetString() { + + String eventSetString = ""; + Pattern eventSetPattern = Pattern.compile(String.format("[0-1]{%d}", eventsPerEventSet)); + + while (!eventSetPattern.matcher(eventSetString).matches()) { + System.out.print("Input 33 coin tosses [0-1]: "); + eventSetString = inputScanner.next(); + } + + return eventSetString; + } + + @Override + protected String getEventValueFormatString(int eventIndexWithinSet) { + return ""; + } + + @Override + protected int toZeroBasedInteger(int eventDecimalValue) { + return eventDecimalValue; + } + + @Override + protected void outputEventValuesLine(StringBuilder eventValuesLine) { } + + @Override + protected StringBuilder getInitialBitsLine() { + return new StringBuilder("| "); + } + + @Override + protected void padBitsLine(StringBuilder eventSetBitsLine, int bitIndexWithinEventSet, int bitIndexWithinEntropy) { + if (bitIndexWithinEventSet == BITS_PER_EVENT_SET - 1) + eventSetBitsLine.append(" |"); + else if (bitIndexWithinEventSet % BITS_PER_WORD == 0 || bitIndexWithinEventSet % BITS_PER_WORD == 6) + eventSetBitsLine.append(styleBit(" ", bitIndexWithinEntropy, bitIndexWithinEventSet)); + else if (bitIndexWithinEventSet % BITS_PER_WORD == BITS_PER_WORD - 1) + eventSetBitsLine.append(" | "); + } + + @Override + protected String getSeedWordLineFormatString() { + return "|%-15s|%-15s|%-15s|"; + } +} \ No newline at end of file diff --git a/src/main/java/io/rudefox/burrow/InteractiveDice8EntropyGenerator.java b/src/main/java/io/rudefox/burrow/InteractiveDice8EntropyGenerator.java new file mode 100644 index 0000000..9bc4fdd --- /dev/null +++ b/src/main/java/io/rudefox/burrow/InteractiveDice8EntropyGenerator.java @@ -0,0 +1,60 @@ +package io.rudefox.burrow; + +import java.util.Scanner; +import java.util.regex.Pattern; + +public class InteractiveDice8EntropyGenerator extends InteractiveEntropyGenerator { + + public InteractiveDice8EntropyGenerator(int targetBitsOfEntropy, Scanner inputScanner, boolean ansiColorOutput) { + super(inputScanner, ansiColorOutput, targetBitsOfEntropy, 3); + } + + @Override + protected String readNextEventSetString() { + + String eventSetString = ""; + Pattern eventSetPattern = Pattern.compile(String.format("[1-8]{%d}", eventsPerEventSet)); + + while (!eventSetPattern.matcher(eventSetString).matches()) { + System.out.print("Input 11 x 8-sided dice rolls [1-8]: "); + eventSetString = inputScanner.next(); + } + + return eventSetString; + } + + @Override + protected String getEventValueFormatString(int eventIndexWithinSet) { + return eventIndexWithinSet == bitsPerEvent || eventIndexWithinSet == 7 ? " %d |" : " %d |"; + } + + @Override + protected int toZeroBasedInteger(int eventDecimalValue) { + return eventDecimalValue - 1; + } + + @Override + protected void outputEventValuesLine(StringBuilder eventValuesLine) { + System.out.println(eventValuesLine); + } + + @Override + protected StringBuilder getInitialBitsLine() { + return new StringBuilder("|"); + } + + @Override + protected void padBitsLine(StringBuilder eventSetBitsLine, int bitIndexWithinEventSet, int bitIndexWithinEntropy) { + if (bitIndexWithinEventSet == BITS_PER_EVENT_SET - 1) + eventSetBitsLine.append("|"); + else if (bitIndexWithinEventSet % bitsPerEvent == bitsPerEvent - 1) + eventSetBitsLine.append(styleBit(" ", bitIndexWithinEntropy, bitIndexWithinEventSet)); + else if (bitIndexWithinEventSet % BITS_PER_WORD == BITS_PER_WORD - 1) + eventSetBitsLine.append(" | "); + } + + @Override + protected String getSeedWordLineFormatString() { + return "|%-15s|%-17s|%-15s|"; + } +} \ No newline at end of file diff --git a/src/main/java/io/rudefox/burrow/InteractiveEntropyGenerator.java b/src/main/java/io/rudefox/burrow/InteractiveEntropyGenerator.java index 0358400..c25144a 100644 --- a/src/main/java/io/rudefox/burrow/InteractiveEntropyGenerator.java +++ b/src/main/java/io/rudefox/burrow/InteractiveEntropyGenerator.java @@ -4,197 +4,151 @@ import com.bjdweck.bitcoin.mnemonic.Entropy; import com.diogonunes.jcolor.AnsiFormat; import java.util.Scanner; -import java.util.regex.Pattern; import static com.diogonunes.jcolor.Attribute.*; -public class InteractiveEntropyGenerator { +public abstract class InteractiveEntropyGenerator { - public static final int BITS_PER_WORD = 11; public static final int WORDS_PER_EVENT_SET = 3; - - enum EntropyBase { TWO, EIGHT } + public static final int BITS_PER_WORD = 11; + public static final int BITS_PER_EVENT_SET = WORDS_PER_EVENT_SET * BITS_PER_WORD; public static final AnsiFormat RED_STYLE = new AnsiFormat(WHITE_TEXT(), RED_BACK(), BOLD()); public static final AnsiFormat GREEN_STYLE = new AnsiFormat(BLACK_TEXT(), GREEN_BACK(), BOLD()); public static final AnsiFormat BLUE_STYLE = new AnsiFormat(WHITE_TEXT(), BLUE_BACK(), BOLD()); - private final EntropyBase entropyBase; - private final int eventsPerSet; - private final int bitsPerEvent; - private final int targetBitsOfEntropy; - private final Scanner inputScanner; - private final boolean ansiColorOutput; - private Entropy entropy = new Entropy(); - public InteractiveEntropyGenerator(int targetBitsOfEntropy, Scanner inputScanner, boolean ansiColorOutput, EntropyBase entropyBase) { + protected final int targetBitsOfEntropy; + protected final int bitsPerEvent; + protected final int eventsPerEventSet; + + protected final Scanner inputScanner; + protected final boolean ansiColorOutput; + + protected InteractiveEntropyGenerator(Scanner inputScanner, boolean ansiColorOutput, int targetBitsOfEntropy, int bitsPerEvent) { this.targetBitsOfEntropy = targetBitsOfEntropy; + this.bitsPerEvent = bitsPerEvent; + this.eventsPerEventSet = BITS_PER_EVENT_SET / this.bitsPerEvent; + this.inputScanner = inputScanner; this.ansiColorOutput = ansiColorOutput; - this.entropyBase = entropyBase; - this.bitsPerEvent = entropyBase == EntropyBase.EIGHT ? 3 : 1; - this.eventsPerSet = BITS_PER_WORD * WORDS_PER_EVENT_SET / bitsPerEvent; } Entropy generate() { entropy = new Entropy(); - for (int eventSetCount = 0; entropy.getBitLength() < targetBitsOfEntropy; eventSetCount++) - doEventSet(eventSetCount); + for (int eventSetIndex = 0; entropy.getBitLength() < targetBitsOfEntropy; eventSetIndex++) { - return entropy.truncate(targetBitsOfEntropy).appendChecksum(); + String eventString = readNextEventSetString(); + StringBuilder eventValuesLine = processEventSetString(eventString); + + System.out.println(); + + outputEventValuesLine(eventValuesLine); + + System.out.println(getBitsLine()); + System.out.println(getSeedWordsLine(eventSetIndex)); + System.out.println(); + } + + Entropy truncatedEntropy = entropy.truncate(targetBitsOfEntropy); + return truncatedEntropy.appendChecksum(); } - private void doEventSet(int currentEventSet) { - - String eventString = - this.entropyBase == EntropyBase.EIGHT ? - readNextDice8EventSetString() : - readNextBinaryEventSetString(); + private StringBuilder processEventSetString(String eventString) { StringBuilder eventValuesLine = new StringBuilder("|"); - for (int eventIndex = 0; eventIndex < eventsPerSet; eventIndex++) { + for (int eventIndexWithinSet = 0; eventIndexWithinSet < eventsPerEventSet; eventIndexWithinSet++) { - int eventValue = Integer.parseInt(eventString.charAt(eventIndex) + ""); + int eventDecimalValue = Integer.parseInt(eventString.charAt(eventIndexWithinSet) + ""); - String formatString = eventIndex == bitsPerEvent || eventIndex == 7 ? " %d |" : " %d |"; + eventValuesLine.append(String.format(getEventValueFormatString(eventIndexWithinSet), eventDecimalValue)); - eventValuesLine.append(String.format(formatString, eventValue)); - - if (entropyBase == EntropyBase.EIGHT) - eventValue--; - - entropy = entropy.appendBits(eventValue, bitsPerEvent); + entropy = entropy.appendBits(toZeroBasedInteger(eventDecimalValue), bitsPerEvent); } - if (entropyBase == EntropyBase.EIGHT) { - System.out.printf("%n%s%n%s%n%s%n%n", - eventValuesLine, - getBitsLine(), - getSeedWordsLine(currentEventSet) - ); - } else { - System.out.printf("%n%s%n%s%n%n", - getBitsLine(), - getSeedWordsLine(currentEventSet) - ); - } - } - - private String readNextDice8EventSetString() { - - String eventSetString = ""; - Pattern eventSetPattern = Pattern.compile(String.format("[1-8]{%d}", eventsPerSet)); - - while (!eventSetPattern.matcher(eventSetString).matches()) { - System.out.print("Input 11 x 8-sided dice rolls [1-8]: "); - eventSetString = inputScanner.next(); - } - - return eventSetString; - } - - private String readNextBinaryEventSetString() { - - String eventSetString = ""; - Pattern eventSetPattern = Pattern.compile(String.format("[0-1]{%d}", eventsPerSet)); - - while (!eventSetPattern.matcher(eventSetString).matches()) { - System.out.print("Input 33 coin tosses [0-1]: "); - eventSetString = inputScanner.next(); - } - - return eventSetString; + return eventValuesLine; } private StringBuilder getBitsLine() { String entropyBitString = entropy.toString(); - String currentEventSetBitString = - entropyBitString.substring(entropyBitString.length() - (BITS_PER_WORD * WORDS_PER_EVENT_SET)); + String eventSetBitString = entropyBitString.substring(entropyBitString.length() - BITS_PER_EVENT_SET); - StringBuilder eventSetBitsLine = new StringBuilder("|"); + StringBuilder bitsLine = getInitialBitsLine(); - if (entropyBase == EntropyBase.TWO) - eventSetBitsLine.append(" "); + for (int bitIndexWithinEventSet = 0; bitIndexWithinEventSet < eventSetBitString.length(); bitIndexWithinEventSet++) { - for (int eventSetBitIndex = 0; eventSetBitIndex < currentEventSetBitString.length(); eventSetBitIndex++) { + int binIndexWithinEntropy = entropyBitString.length() - eventSetBitString.length() + bitIndexWithinEventSet; - int entropyBitIndex = entropyBitString.length() - currentEventSetBitString.length() + eventSetBitIndex; - - if (entropyBitIndex < targetBitsOfEntropy) - eventSetBitsLine.append(styleBit("" + currentEventSetBitString.charAt(eventSetBitIndex), entropyBitIndex, eventSetBitIndex)); + if (binIndexWithinEntropy < targetBitsOfEntropy) + bitsLine.append(styleBit("" + eventSetBitString.charAt(bitIndexWithinEventSet), binIndexWithinEntropy, bitIndexWithinEventSet)); else - eventSetBitsLine.append("-"); + bitsLine.append("-"); - if (entropyBase == EntropyBase.EIGHT) { - if (eventSetBitIndex == 32) - eventSetBitsLine.append("|"); - else if (eventSetBitIndex % bitsPerEvent == bitsPerEvent - 1) - eventSetBitsLine.append(styleBit(" ", entropyBitIndex, eventSetBitIndex)); - else if (eventSetBitIndex % BITS_PER_WORD == BITS_PER_WORD - 1) - eventSetBitsLine.append(" | "); - } - - if (entropyBase == EntropyBase.TWO) { - if (eventSetBitIndex == 32) - eventSetBitsLine.append(" |"); - else if (eventSetBitIndex % BITS_PER_WORD == 0 || eventSetBitIndex % BITS_PER_WORD == 6) - eventSetBitsLine.append(styleBit(" ", entropyBitIndex, eventSetBitIndex)); - else if (eventSetBitIndex % BITS_PER_WORD == BITS_PER_WORD - 1) - eventSetBitsLine.append(" | "); - } + padBitsLine(bitsLine, bitIndexWithinEventSet, binIndexWithinEntropy); } - return eventSetBitsLine; + return bitsLine; } - private String styleBit(String bit, int globalEntropyBitIndex, int currentRollSetBitIndex) { + String styleBit(String bit, int bitIndexWithinEntropy, int bitIndexWithinEventSet) { - if (!ansiColorOutput || globalEntropyBitIndex >= targetBitsOfEntropy) + if (!ansiColorOutput || bitIndexWithinEntropy >= targetBitsOfEntropy) return bit; - return getBitFormat(currentRollSetBitIndex).format(bit); + return getBitColor(bitIndexWithinEventSet).format(bit); } - private AnsiFormat getBitFormat(int eventSetBitIndex) { + private AnsiFormat getBitColor(int bitIndexWithinEventSet) { - int wordBitIndex = eventSetBitIndex % BITS_PER_WORD; + int bitIndexWithinWord = bitIndexWithinEventSet % BITS_PER_WORD; - if (wordBitIndex == 0) + if (bitIndexWithinWord == 0) return RED_STYLE; - if (wordBitIndex <= 6) + if (bitIndexWithinWord <= 6) return GREEN_STYLE; return BLUE_STYLE; } - private String getSeedWordsLine(int rollSet) { + private String getSeedWordsLine(int eventSetIndex) { - int baseIndex = rollSet * WORDS_PER_EVENT_SET; - - String format = entropyBase == EntropyBase.EIGHT ? - "|%-15s|%-17s|%-15s|" : "|%-15s|%-15s|%-15s|"; + int firstWordIndex = eventSetIndex * WORDS_PER_EVENT_SET; return String.format( - format, - getFormattedSeedWord(baseIndex), - getFormattedSeedWord(baseIndex + 1), - getFormattedSeedWord(baseIndex + 2)); + getSeedWordLineFormatString(), + getFormattedSeedWord(firstWordIndex), + getFormattedSeedWord(firstWordIndex + 1), + getFormattedSeedWord(firstWordIndex + 2)); } - private String getFormattedSeedWord(int index) { + private String getFormattedSeedWord(int wordIndex) { - int lastWordIndex = (WORDS_PER_EVENT_SET * targetBitsOfEntropy / 32) - 1; + int lastWordIndex = WORDS_PER_EVENT_SET * targetBitsOfEntropy / (BITS_PER_EVENT_SET - 1) - 1; - String seedWord = index != lastWordIndex ? entropy.getWord(index) : "CHECKWORD"; + String seedWord = wordIndex != lastWordIndex ? entropy.getWord(wordIndex) : "CHECKWORD"; - return String.format(" %2d. %s", index + 1, seedWord); + return String.format(" %2d. %s", wordIndex + 1, seedWord); } -} \ No newline at end of file + + protected abstract String readNextEventSetString(); + + protected abstract String getEventValueFormatString(int eventIndexWithinSet); + + protected abstract int toZeroBasedInteger(int eventDecimalValue); + + protected abstract void outputEventValuesLine(StringBuilder eventValuesLine); + + protected abstract void padBitsLine(StringBuilder eventSetBitsLine, int bitIndexWithinEventSet, int bitIndexWithinEntropy); + + protected abstract StringBuilder getInitialBitsLine(); + + protected abstract String getSeedWordLineFormatString(); +} diff --git a/src/main/java/io/rudefox/burrow/MnemonicCommand.java b/src/main/java/io/rudefox/burrow/MnemonicCommand.java index 8053932..19369eb 100644 --- a/src/main/java/io/rudefox/burrow/MnemonicCommand.java +++ b/src/main/java/io/rudefox/burrow/MnemonicCommand.java @@ -54,7 +54,7 @@ public class MnemonicCommand implements Runnable { static class EventMethod { - @CommandLine.Option(names = {"-e", "--events"}, paramLabel = "[1-6]{100}|[1-8]{86}", + @CommandLine.Option(names = {"-e", "--events"}, paramLabel = "[0-1]{256} | [1-6]{100} | [1-8]{86}", description = "string representing events from entropy source", required = true) String getEventString; @@ -83,25 +83,28 @@ public class MnemonicCommand implements Runnable { if (!isCustomEntropy()) return generateEntropy(); - EventBuffer eventBuffer; - - if (entropyOptions.isBinaryEntropy) - eventBuffer = new EventBuffer(this.targetBitsOfEntropy, 2); - else if (entropyOptions.isDice6Entropy) - eventBuffer = new EventBuffer(this.targetBitsOfEntropy, 6); - else - eventBuffer = new EventBuffer(this.targetBitsOfEntropy, 8); + if (isBinaryInteractiveMode()) + return new InteractiveBinaryEntropyGenerator(targetBitsOfEntropy, new Scanner(System.in), CommandLine.Help.Ansi.AUTO.enabled()).generate(); if (isDice6InteractiveMode()) - return getDice6EntropyInteractive(eventBuffer); + return getDice6EntropyInteractive(new EventBuffer(this.targetBitsOfEntropy, 6)); if (isDice8InteractiveMode()) - return new InteractiveEntropyGenerator(targetBitsOfEntropy, new Scanner(System.in), CommandLine.Help.Ansi.AUTO.enabled(), InteractiveEntropyGenerator.EntropyBase.EIGHT).generate(); + return new InteractiveDice8EntropyGenerator(targetBitsOfEntropy, new Scanner(System.in), CommandLine.Help.Ansi.AUTO.enabled()).generate(); - if (isBinaryInteractiveMode()) - return new InteractiveEntropyGenerator(targetBitsOfEntropy, new Scanner(System.in), CommandLine.Help.Ansi.AUTO.enabled(), InteractiveEntropyGenerator.EntropyBase.TWO).generate(); + EventBuffer eventBuffer; + + if (entropyOptions.isBinaryEntropy) { + eventBuffer = new EventBuffer(this.targetBitsOfEntropy, 2); + eventBuffer.appendBinaryEvents(entropyOptions.eventMethod.getEventString); + } else if (entropyOptions.isDice6Entropy) { + eventBuffer = new EventBuffer(this.targetBitsOfEntropy, 6); + eventBuffer.appendD6Events(entropyOptions.eventMethod.getEventString); + } else { + eventBuffer = new EventBuffer(this.targetBitsOfEntropy, 8); + eventBuffer.appendD8Events(entropyOptions.eventMethod.getEventString); + } - eventBuffer.appendEvents(entropyOptions.eventMethod.getEventString); return eventBuffer.toEntropy(); } @@ -120,7 +123,7 @@ public class MnemonicCommand implements Runnable { String inputString = scanner.next(); - eventBuffer.appendEvents(inputString); + eventBuffer.appendD6Events(inputString); firstIteration = false; } diff --git a/src/test/java/io/rudefox/burrow/mnemonic_command_tests.java b/src/test/java/io/rudefox/burrow/mnemonic_command_tests.java index e6457d0..0518967 100644 --- a/src/test/java/io/rudefox/burrow/mnemonic_command_tests.java +++ b/src/test/java/io/rudefox/burrow/mnemonic_command_tests.java @@ -49,6 +49,18 @@ class mnemonic_command_tests extends CliTestFixture { verifyOutput(); } + @Test + void with_arguments_non_interactive_binary_entropy_should_generate_mnemonic_sentence() throws UnsupportedEncodingException { + + withArgs("mnemonic --binary-entropy --events 10100101010010101010101000101010101001010100101010101010001010101010010101001010101010100010101010100101010010101010101000101010 --bits 128"); + + expectedOutput("pipe fetch melt enhance primary best news fetch click clean pride feed" + EOL); + + doMain(); + + verifyOutput(); + } + @Test void with_arguments_without_interactive_dice_should_generate_mnemonic_sentence() throws UnsupportedEncodingException { @@ -62,6 +74,18 @@ class mnemonic_command_tests extends CliTestFixture { verifyOutput(); } + @Test + void with_arguments_without_interactive_d8_dice_should_generate_mnemonic_sentence() throws UnsupportedEncodingException { + + withArgs("mnemonic --dice8-entropy --events 23438688738737812541514768125415147632873218 --bits 128"); + + expectedOutput("fashion wave tourist notice alert marine vote alert marine vocal dish aware" + EOL); + + doMain(); + + verifyOutput(); + } + private void doMain() { setInput();