155 lines
5.3 KiB
Java
155 lines
5.3 KiB
Java
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();
|
|
}
|