package io.rudefox.burrow; 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 static final int BITS_PER_WORD = 11; public static final int WORDS_PER_EVENT_SET = 3; enum EntropyBase { TWO, EIGHT } 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) { this.targetBitsOfEntropy = targetBitsOfEntropy; 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); return entropy.truncate(targetBitsOfEntropy).appendChecksum(); } private void doEventSet(int currentEventSet) { String eventString = this.entropyBase == EntropyBase.EIGHT ? readNextDice8EventSetString() : readNextBinaryEventSetString(); StringBuilder eventValuesLine = new StringBuilder("|"); for (int eventIndex = 0; eventIndex < eventsPerSet; eventIndex++) { int eventValue = Integer.parseInt(eventString.charAt(eventIndex) + ""); String formatString = eventIndex == bitsPerEvent || eventIndex == 7 ? " %d |" : " %d |"; eventValuesLine.append(String.format(formatString, eventValue)); if (entropyBase == EntropyBase.EIGHT) eventValue--; entropy = entropy.appendBits(eventValue, 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; } private StringBuilder getBitsLine() { String entropyBitString = entropy.toString(); String currentEventSetBitString = entropyBitString.substring(entropyBitString.length() - (BITS_PER_WORD * WORDS_PER_EVENT_SET)); StringBuilder eventSetBitsLine = new StringBuilder("|"); if (entropyBase == EntropyBase.TWO) eventSetBitsLine.append(" "); for (int eventSetBitIndex = 0; eventSetBitIndex < currentEventSetBitString.length(); eventSetBitIndex++) { int entropyBitIndex = entropyBitString.length() - currentEventSetBitString.length() + eventSetBitIndex; if (entropyBitIndex < targetBitsOfEntropy) eventSetBitsLine.append(styleBit("" + currentEventSetBitString.charAt(eventSetBitIndex), entropyBitIndex, eventSetBitIndex)); else eventSetBitsLine.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(" | "); } } return eventSetBitsLine; } private String styleBit(String bit, int globalEntropyBitIndex, int currentRollSetBitIndex) { if (!ansiColorOutput || globalEntropyBitIndex >= targetBitsOfEntropy) return bit; return getBitFormat(currentRollSetBitIndex).format(bit); } private AnsiFormat getBitFormat(int eventSetBitIndex) { int wordBitIndex = eventSetBitIndex % BITS_PER_WORD; if (wordBitIndex == 0) return RED_STYLE; if (wordBitIndex <= 6) return GREEN_STYLE; return BLUE_STYLE; } private String getSeedWordsLine(int rollSet) { int baseIndex = rollSet * WORDS_PER_EVENT_SET; String format = entropyBase == EntropyBase.EIGHT ? "|%-15s|%-17s|%-15s|" : "|%-15s|%-15s|%-15s|"; return String.format( format, getFormattedSeedWord(baseIndex), getFormattedSeedWord(baseIndex + 1), getFormattedSeedWord(baseIndex + 2)); } private String getFormattedSeedWord(int index) { int lastWordIndex = (WORDS_PER_EVENT_SET * targetBitsOfEntropy / 32) - 1; String seedWord = index != lastWordIndex ? entropy.getWord(index) : "CHECKWORD"; return String.format(" %2d. %s", index + 1, seedWord); } }