200 lines
6.9 KiB
Java
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);
|
||
|
}
|
||
|
}
|