package io.rudefox.burrow; import com.bjdweck.bitcoin.mnemonic.Entropy; import com.diogonunes.jcolor.AnsiFormat; import java.util.Scanner; import static com.diogonunes.jcolor.Attribute.*; public abstract class InteractiveEntropyGenerator { public static final int WORDS_PER_EVENT_SET = 3; 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 Entropy entropy = new Entropy(); 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; } Entropy generate() { entropy = new Entropy(); for (int eventSetIndex = 0; entropy.getBitLength() < targetBitsOfEntropy; eventSetIndex++) { 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 StringBuilder processEventSetString(String eventString) { StringBuilder eventValuesLine = new StringBuilder("|"); for (int eventIndexWithinSet = 0; eventIndexWithinSet < eventsPerEventSet; eventIndexWithinSet++) { int eventDecimalValue = Integer.parseInt(eventString.charAt(eventIndexWithinSet) + ""); eventValuesLine.append(String.format(getEventValueFormatString(eventIndexWithinSet), eventDecimalValue)); entropy = entropy.appendBits(toZeroBasedInteger(eventDecimalValue), bitsPerEvent); } return eventValuesLine; } private StringBuilder getBitsLine() { String entropyBitString = entropy.toString(); String eventSetBitString = entropyBitString.substring(entropyBitString.length() - BITS_PER_EVENT_SET); StringBuilder bitsLine = getInitialBitsLine(); for (int bitIndexWithinEventSet = 0; bitIndexWithinEventSet < eventSetBitString.length(); bitIndexWithinEventSet++) { int binIndexWithinEntropy = entropyBitString.length() - eventSetBitString.length() + bitIndexWithinEventSet; if (binIndexWithinEntropy < targetBitsOfEntropy) bitsLine.append(styleBit("" + eventSetBitString.charAt(bitIndexWithinEventSet), binIndexWithinEntropy, bitIndexWithinEventSet)); else bitsLine.append("-"); padBitsLine(bitsLine, bitIndexWithinEventSet, binIndexWithinEntropy); } return bitsLine; } String styleBit(String bit, int bitIndexWithinEntropy, int bitIndexWithinEventSet) { if (!ansiColorOutput || bitIndexWithinEntropy >= targetBitsOfEntropy) return bit; return getBitColor(bitIndexWithinEventSet).format(bit); } private AnsiFormat getBitColor(int bitIndexWithinEventSet) { int bitIndexWithinWord = bitIndexWithinEventSet % BITS_PER_WORD; if (bitIndexWithinWord == 0) return RED_STYLE; if (bitIndexWithinWord <= 6) return GREEN_STYLE; return BLUE_STYLE; } private String getSeedWordsLine(int eventSetIndex) { int firstWordIndex = eventSetIndex * WORDS_PER_EVENT_SET; return String.format( getSeedWordLineFormatString(), getFormattedSeedWord(firstWordIndex), getFormattedSeedWord(firstWordIndex + 1), getFormattedSeedWord(firstWordIndex + 2)); } private String getFormattedSeedWord(int wordIndex) { int lastWordIndex = WORDS_PER_EVENT_SET * targetBitsOfEntropy / (BITS_PER_EVENT_SET - 1) - 1; String seedWord = wordIndex != lastWordIndex ? entropy.getWord(wordIndex) : "CHECKWORD"; return String.format(" %2d. %s", wordIndex + 1, seedWord); } 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(); }