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 isCustomEntropy() { return entropyOptions != null && (entropyOptions.isDice6Entropy || entropyOptions.isDice8Entropy || entropyOptions.isBinaryEntropy); } private boolean isInteractiveMode() { return entropyOptions != null && entropyOptions.eventMethod.isInteractiveMode; } private boolean isBinaryInteractiveMode() { return isInteractiveMode() && entropyOptions.isBinaryEntropy; } 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 = {"-2", "--binary-entropy"}, description = "use a coin toss or other binary entropy source") boolean isBinaryEntropy; @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 = "[0-1]{256} | [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 (!isCustomEntropy()) return generateEntropy(); if (isBinaryInteractiveMode()) return new InteractiveBinaryEntropyGenerator(targetBitsOfEntropy, new Scanner(System.in), CommandLine.Help.Ansi.AUTO.enabled()).generate(); if (isDice6InteractiveMode()) return getDice6EntropyInteractive(new EventBuffer(this.targetBitsOfEntropy, 6)); if (isDice8InteractiveMode()) return new InteractiveDice8EntropyGenerator(targetBitsOfEntropy, new Scanner(System.in), CommandLine.Help.Ansi.AUTO.enabled()).generate(); EventBuffer eventBuffer; if (entropyOptions.isBinaryEntropy) { eventBuffer = new EventBuffer(this.targetBitsOfEntropy, 2); eventBuffer.appendBinaryEvents(entropyOptions.eventMethod.getEventString); } else if (entropyOptions.isDice6Entropy) { eventBuffer = new EventBuffer(this.targetBitsOfEntropy, 6); eventBuffer.appendD6Events(entropyOptions.eventMethod.getEventString); } else { eventBuffer = new EventBuffer(this.targetBitsOfEntropy, 8); eventBuffer.appendD8Events(entropyOptions.eventMethod.getEventString); } return eventBuffer.toEntropy(); } private Entropy getDice6EntropyInteractive(EventBuffer eventBuffer) { int requiredEvents = eventBuffer.getRequiredEvents(); Scanner scanner = new Scanner(System.in); boolean firstIteration = true; while (eventBuffer.events() < requiredEvents) { int remainingEvents = requiredEvents - eventBuffer.events(); System.out.printf("Input %d %sdice rolls [1-6]: ", remainingEvents, (firstIteration ? "" : "more ")); String inputString = scanner.next(); eventBuffer.appendD6Events(inputString); firstIteration = false; } return eventBuffer.toEntropy(); } private Entropy generateEntropy() { SecureRandom random = new SecureRandom(); int byteLength = targetBitsOfEntropy / 8; byte[] randomBytes = new byte[byteLength]; random.nextBytes(randomBytes); return Entropy.fromRawEntropy(randomBytes); } }