burrow/src/main/java/io/rudefox/burrow/InteractiveEntropyGenerator...

200 lines
6.9 KiB
Java

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);
}
}