package io.rudefox.burrow; import com.bjdweck.bitcoin.mnemonic.Entropy; import picocli.CommandLine; import java.security.SecureRandom; import java.util.Scanner; @CommandLine.Command(name = "mnemonic", description = "generate mnemonic sentence") public class MnemonicCommand implements Runnable { private static final int DEFAULT_BITS_OF_ENTROPY = 256; private boolean isDiceEntropy() { return entropyOptions != null && (entropyOptions.isDice6Entropy || entropyOptions.isDice8Entropy); } private boolean isInteractiveMode() { return entropyOptions != null && entropyOptions.eventMethod.isInteractiveMode; } private boolean isDice6InteractiveMode() { return isInteractiveMode() && entropyOptions.isDice6Entropy; } private boolean isDice8InteractiveMode() { return isInteractiveMode() && entropyOptions.isDice8Entropy; } @CommandLine.ArgGroup(exclusive = false) EntropyOptions entropyOptions; static class EntropyOptions { @CommandLine.Option(names = {"-6", "--dice6-entropy"}, description = "use 6-sided dice entropy source") boolean isDice6Entropy; @CommandLine.Option(names = {"-8", "--dice8-entropy"}, description = "use 8-sided dice entropy source") boolean isDice8Entropy; @CommandLine.ArgGroup(multiplicity = "1") EventMethod eventMethod; static class EventMethod { @CommandLine.Option(names = {"-e", "--events"}, paramLabel = "[1-6]{100}|[1-8]{86}", description = "string representing events from entropy source", required = true) String getEventString; @CommandLine.Option(names = {"-i", "--interactive"}, description = "use interactive command line mode", required = true) boolean isInteractiveMode; } } @CommandLine.Option(names = {"-b", "--bits"}, defaultValue = DEFAULT_BITS_OF_ENTROPY+"", description = "bits of entropy (default: 256)", paramLabel = "128|160|192|224|256") int targetBitsOfEntropy; public void run() { Entropy entropyBytes = getEntropy(); System.out.println(entropyBytes.toMnemonic().getSentence()); } Entropy getEntropy() { if (!isDiceEntropy()) return getGeneratedEntropy(); DiceEventBuffer diceEventBuffer; if (entropyOptions.isDice6Entropy) diceEventBuffer = new DiceEventBuffer(this.targetBitsOfEntropy, 6); else diceEventBuffer = new DiceEventBuffer(this.targetBitsOfEntropy, 8); if (isDice6InteractiveMode()) return getDice6EntropyInteractive(diceEventBuffer); if (isDice8InteractiveMode()) return new Dice8EntropyGenerator(targetBitsOfEntropy, new Scanner(System.in), CommandLine.Help.Ansi.AUTO.enabled()).generate(); diceEventBuffer.appendEvents(entropyOptions.eventMethod.getEventString); return diceEventBuffer.toEntropy(); } private Entropy getDice6EntropyInteractive(DiceEventBuffer diceEventBuffer) { int requiredEvents = diceEventBuffer.getRequiredEvents(); Scanner scanner = new Scanner(System.in); boolean firstIteration = true; while (diceEventBuffer.events() < requiredEvents) { int remainingEvents = requiredEvents - diceEventBuffer.events(); System.out.print(String.format("Input %d %sdice rolls [1-6]: ", remainingEvents, (firstIteration ? "" : "more "))); String inputString = scanner.next(); diceEventBuffer.appendEvents(inputString); firstIteration = false; } return diceEventBuffer.toEntropy(); } private Entropy getGeneratedEntropy() { SecureRandom random = new SecureRandom(); int byteLength = targetBitsOfEntropy / 8; byte[] randomBytes = new byte[byteLength]; random.nextBytes(randomBytes); return Entropy.fromRawEntropy(randomBytes); } }