diff --git a/build.gradle b/build.gradle index 6c78668..087e19a 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ group 'io.rudefox' version isRelease ? gitVersion.call() : gitVersion.call() + "-SNAPSHOT" application { - mainClassName = 'io.rudefox.cold.RudefoxCold' + mainClassName = 'io.rudefox.burrow.RudefoxBurrow' } run { diff --git a/src/main/java/io/rudefox/cold/Dice8EntropyGenerator.java b/src/main/java/io/rudefox/burrow/Dice8EntropyGenerator.java similarity index 96% rename from src/main/java/io/rudefox/cold/Dice8EntropyGenerator.java rename to src/main/java/io/rudefox/burrow/Dice8EntropyGenerator.java index aa16b56..6522826 100644 --- a/src/main/java/io/rudefox/cold/Dice8EntropyGenerator.java +++ b/src/main/java/io/rudefox/burrow/Dice8EntropyGenerator.java @@ -1,115 +1,115 @@ -package io.rudefox.cold; - -import com.bjdweck.bitcoin.mnemonic.Entropy; - -import java.util.Scanner; -import java.util.regex.Pattern; - -public class Dice8EntropyGenerator { - - public static final int DICE_PER_ROLL = 11; - - private final int targetBitsOfEntropy; - private final Scanner inputScanner; - - private Entropy entropy = new Entropy(); - - public Dice8EntropyGenerator(int targetBitsOfEntropy, Scanner inputScanner) { - - this.targetBitsOfEntropy = targetBitsOfEntropy; - this.inputScanner = inputScanner; - } - - Entropy generate() { - - entropy = new Entropy(); - - for (int rollSetCount = 0; entropy.getBitLength() < targetBitsOfEntropy; rollSetCount++) - doDiceRoll(rollSetCount); - - return entropy.truncate(targetBitsOfEntropy).appendChecksum(); - } - - private void doDiceRoll(int currentRollSet) { - - String eventString = scanNextRollSetString(); - - StringBuilder rollValuesLine = new StringBuilder("|"); - - for (int rollNumber = 0; rollNumber < DICE_PER_ROLL; rollNumber++) { - - int rollValue = Integer.parseInt(eventString.charAt(rollNumber) + ""); - - String formatString = - rollNumber == 3 || rollNumber == 7 ? " %d |" : " %d |"; - - rollValuesLine.append(String.format(formatString, rollValue)); - - entropy = entropy.appendBits(rollValue - 1, 3); - } - - System.out.printf("%n%s%n%s%n%s%n%n", - rollValuesLine, - getBitsLine(), - getSeedWordsLine(currentRollSet) - ); - } - - private String scanNextRollSetString() { - - String rollSetString = ""; - Pattern rollSetPattern = Pattern.compile(String.format("[1-8]{%d}", DICE_PER_ROLL)); - - while (!rollSetPattern.matcher(rollSetString).matches()) { - System.out.print("Input 11 x 8-sided dice rolls [1-8]: "); - rollSetString = inputScanner.next(); - } - - return rollSetString; - } - - private StringBuilder getBitsLine() { - - String entropyBitString = entropy.toString(); - - String currentRollSetBitString = - entropyBitString.substring(entropyBitString.length() - (DICE_PER_ROLL * 3)); - - StringBuilder rollSetBitsLine = new StringBuilder("|"); - for (int rollSetBitIndex = 0; rollSetBitIndex < currentRollSetBitString.length(); rollSetBitIndex++) { - - int entropyBitIndex = entropyBitString.length() - currentRollSetBitString.length() + rollSetBitIndex; - - if (entropyBitIndex < targetBitsOfEntropy) - rollSetBitsLine.append(currentRollSetBitString.charAt(rollSetBitIndex)); - else - rollSetBitsLine.append("-"); - - if (rollSetBitIndex == 32) rollSetBitsLine.append("|"); - else if (rollSetBitIndex % 3 == 2) rollSetBitsLine.append(" "); - else if (rollSetBitIndex % 11 == 10) rollSetBitsLine.append(" | "); - } - - return rollSetBitsLine; - } - - private String getSeedWordsLine(int rollSet) { - - int baseIndex = rollSet * 3; - - return String.format( - "|%-15s|%-17s|%-15s|", - getFormattedSeedWord(baseIndex), - getFormattedSeedWord(baseIndex + 1), - getFormattedSeedWord(baseIndex + 2)); - } - - private String getFormattedSeedWord(int index) { - - int lastWordIndex = (3 * targetBitsOfEntropy / 32) - 1; - - String seedWord = index != lastWordIndex ? entropy.getWord(index) : "CHECKWORD"; - - return String.format(" %2d. %s", index + 1, seedWord); - } +package io.rudefox.burrow; + +import com.bjdweck.bitcoin.mnemonic.Entropy; + +import java.util.Scanner; +import java.util.regex.Pattern; + +public class Dice8EntropyGenerator { + + public static final int DICE_PER_ROLL = 11; + + private final int targetBitsOfEntropy; + private final Scanner inputScanner; + + private Entropy entropy = new Entropy(); + + public Dice8EntropyGenerator(int targetBitsOfEntropy, Scanner inputScanner) { + + this.targetBitsOfEntropy = targetBitsOfEntropy; + this.inputScanner = inputScanner; + } + + Entropy generate() { + + entropy = new Entropy(); + + for (int rollSetCount = 0; entropy.getBitLength() < targetBitsOfEntropy; rollSetCount++) + doDiceRoll(rollSetCount); + + return entropy.truncate(targetBitsOfEntropy).appendChecksum(); + } + + private void doDiceRoll(int currentRollSet) { + + String eventString = scanNextRollSetString(); + + StringBuilder rollValuesLine = new StringBuilder("|"); + + for (int rollNumber = 0; rollNumber < DICE_PER_ROLL; rollNumber++) { + + int rollValue = Integer.parseInt(eventString.charAt(rollNumber) + ""); + + String formatString = + rollNumber == 3 || rollNumber == 7 ? " %d |" : " %d |"; + + rollValuesLine.append(String.format(formatString, rollValue)); + + entropy = entropy.appendBits(rollValue - 1, 3); + } + + System.out.printf("%n%s%n%s%n%s%n%n", + rollValuesLine, + getBitsLine(), + getSeedWordsLine(currentRollSet) + ); + } + + private String scanNextRollSetString() { + + String rollSetString = ""; + Pattern rollSetPattern = Pattern.compile(String.format("[1-8]{%d}", DICE_PER_ROLL)); + + while (!rollSetPattern.matcher(rollSetString).matches()) { + System.out.print("Input 11 x 8-sided dice rolls [1-8]: "); + rollSetString = inputScanner.next(); + } + + return rollSetString; + } + + private StringBuilder getBitsLine() { + + String entropyBitString = entropy.toString(); + + String currentRollSetBitString = + entropyBitString.substring(entropyBitString.length() - (DICE_PER_ROLL * 3)); + + StringBuilder rollSetBitsLine = new StringBuilder("|"); + for (int rollSetBitIndex = 0; rollSetBitIndex < currentRollSetBitString.length(); rollSetBitIndex++) { + + int entropyBitIndex = entropyBitString.length() - currentRollSetBitString.length() + rollSetBitIndex; + + if (entropyBitIndex < targetBitsOfEntropy) + rollSetBitsLine.append(currentRollSetBitString.charAt(rollSetBitIndex)); + else + rollSetBitsLine.append("-"); + + if (rollSetBitIndex == 32) rollSetBitsLine.append("|"); + else if (rollSetBitIndex % 3 == 2) rollSetBitsLine.append(" "); + else if (rollSetBitIndex % 11 == 10) rollSetBitsLine.append(" | "); + } + + return rollSetBitsLine; + } + + private String getSeedWordsLine(int rollSet) { + + int baseIndex = rollSet * 3; + + return String.format( + "|%-15s|%-17s|%-15s|", + getFormattedSeedWord(baseIndex), + getFormattedSeedWord(baseIndex + 1), + getFormattedSeedWord(baseIndex + 2)); + } + + private String getFormattedSeedWord(int index) { + + int lastWordIndex = (3 * targetBitsOfEntropy / 32) - 1; + + String seedWord = index != lastWordIndex ? entropy.getWord(index) : "CHECKWORD"; + + return String.format(" %2d. %s", index + 1, seedWord); + } } \ No newline at end of file diff --git a/src/main/java/io/rudefox/cold/DiceEventBuffer.java b/src/main/java/io/rudefox/burrow/DiceEventBuffer.java similarity index 94% rename from src/main/java/io/rudefox/cold/DiceEventBuffer.java rename to src/main/java/io/rudefox/burrow/DiceEventBuffer.java index d388c79..69cc0fd 100644 --- a/src/main/java/io/rudefox/cold/DiceEventBuffer.java +++ b/src/main/java/io/rudefox/burrow/DiceEventBuffer.java @@ -1,47 +1,47 @@ -package io.rudefox.cold; - -import com.bjdweck.bitcoin.mnemonic.Entropy; -import com.bjdweck.math.UnsignedInt; - -class DiceEventBuffer { - - private final int diceBase; - private final StringBuilder buffer; - private final int targetBitsOfEntropy; - - DiceEventBuffer(int targetBitsOfEntropy, int diceBase) { - - this.diceBase = diceBase; - this.targetBitsOfEntropy = targetBitsOfEntropy; - this.buffer = new StringBuilder(getRequiredEvents()); - } - - int getRequiredEvents() { - return (int) Math.ceil(this.targetBitsOfEntropy * Math.log(2) / Math.log(diceBase)); - } - - void appendEvents(String eventString) { - for (char inChar : eventString.toCharArray()) - if (inChar >= '1' && inChar <= '6' && events() < getRequiredEvents()) - append((char) (inChar - 1)); - } - - Entropy toEntropy() { - UnsignedInt entropy = new UnsignedInt(toString(), diceBase); - byte[] entropyBytes = entropy.getLowestOrderBits(this.targetBitsOfEntropy).toBigEndianByteArray(); - return Entropy.fromRawEntropy(entropyBytes); - } - - int events() { - return buffer.length(); - } - - private void append(char c) { - buffer.append(c); - } - - @Override - public String toString() { - return buffer.toString(); - } +package io.rudefox.burrow; + +import com.bjdweck.bitcoin.mnemonic.Entropy; +import com.bjdweck.math.UnsignedInt; + +class DiceEventBuffer { + + private final int diceBase; + private final StringBuilder buffer; + private final int targetBitsOfEntropy; + + DiceEventBuffer(int targetBitsOfEntropy, int diceBase) { + + this.diceBase = diceBase; + this.targetBitsOfEntropy = targetBitsOfEntropy; + this.buffer = new StringBuilder(getRequiredEvents()); + } + + int getRequiredEvents() { + return (int) Math.ceil(this.targetBitsOfEntropy * Math.log(2) / Math.log(diceBase)); + } + + void appendEvents(String eventString) { + for (char inChar : eventString.toCharArray()) + if (inChar >= '1' && inChar <= '6' && events() < getRequiredEvents()) + append((char) (inChar - 1)); + } + + Entropy toEntropy() { + UnsignedInt entropy = new UnsignedInt(toString(), diceBase); + byte[] entropyBytes = entropy.getLowestOrderBits(this.targetBitsOfEntropy).toBigEndianByteArray(); + return Entropy.fromRawEntropy(entropyBytes); + } + + int events() { + return buffer.length(); + } + + private void append(char c) { + buffer.append(c); + } + + @Override + public String toString() { + return buffer.toString(); + } } \ No newline at end of file diff --git a/src/main/java/io/rudefox/cold/MnemonicCommand.java b/src/main/java/io/rudefox/burrow/MnemonicCommand.java similarity index 96% rename from src/main/java/io/rudefox/cold/MnemonicCommand.java rename to src/main/java/io/rudefox/burrow/MnemonicCommand.java index 98f8f7d..ad4c19b 100644 --- a/src/main/java/io/rudefox/cold/MnemonicCommand.java +++ b/src/main/java/io/rudefox/burrow/MnemonicCommand.java @@ -1,125 +1,125 @@ -package io.rudefox.cold; - -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)).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); - } +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)).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); + } } \ No newline at end of file diff --git a/src/main/java/io/rudefox/cold/QRCode.java b/src/main/java/io/rudefox/burrow/QRCode.java similarity index 97% rename from src/main/java/io/rudefox/cold/QRCode.java rename to src/main/java/io/rudefox/burrow/QRCode.java index 9d17ba3..0a77c94 100644 --- a/src/main/java/io/rudefox/cold/QRCode.java +++ b/src/main/java/io/rudefox/burrow/QRCode.java @@ -1,111 +1,111 @@ -package io.rudefox.cold; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.WriterException; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.qrcode.QRCodeWriter; -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -class QRCode { - - private static final String FALLBACK_SET_STRING = "##"; - - static String toQRCode(String data) { - - QRCodeWriter qrCodeWriter = new QRCodeWriter(); - Map hints = new HashMap<>(); - hints.put(EncodeHintType.MARGIN, 0); - hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); - - BitMatrix bitMatrix = null; - try { - bitMatrix = qrCodeWriter.encode(data, BarcodeFormat.QR_CODE, 0, 0, hints); - } catch (WriterException e) { - e.printStackTrace(); - } - - if (bitMatrix == null) - return "[Error Generating QR Code]"; - - if (charsetDoesNotSupportFullBlockChar()) - return bitMatrix.toString(FALLBACK_SET_STRING, " "); - - return toUTF8(bitMatrix); - } - - private static String toUTF8(BitMatrix bitMatrix) { - - char SPACE = '\u0020'; - char LOWER_BLOCK = '\u2584'; - char UPPER_BLOCK = '\u2580'; - char FULL_BLOCK = '\u2588'; - - int[] blockCodepoints = new int[] {SPACE, LOWER_BLOCK, UPPER_BLOCK, FULL_BLOCK}; - - StringBuilder qrCode = new StringBuilder(); - - for (int y = 0; y < bitMatrix.getHeight(); y += 2) { - - for (int x = 0; x < bitMatrix.getWidth(); x++) { - - int upper_bit = bitMatrix.get(x, y) ? 1 : 0; - int lower_bit = y+1 < bitMatrix.getHeight() && bitMatrix.get(x, y+1) ? 1 : 0; - - int index = upper_bit << 1 | lower_bit; - int blockCodepoint = blockCodepoints[index]; - - qrCode.appendCodePoint(blockCodepoint); - } - - if (y < bitMatrix.getHeight() - 1) - qrCode.append("\n"); - } - - return qrCode.toString(); - } - - private static boolean charsetDoesNotSupportFullBlockChar() { - - Charset charset = ServiceLocator.DEFAULT_CHARSET; - String charsetString = charset.toString(); - - return charset.equals(StandardCharsets.ISO_8859_1) || - charset.equals(StandardCharsets.US_ASCII) || - charsetString.equalsIgnoreCase("euc-jp") || - charsetString.equalsIgnoreCase("euc-kr") || - charsetString.equalsIgnoreCase("iso-2022-jp") || - charsetString.equalsIgnoreCase("iso-8859-10") || - charsetString.equalsIgnoreCase("iso-8859-13") || - charsetString.equalsIgnoreCase("iso-8859-14") || - charsetString.equalsIgnoreCase("iso-8859-15") || - charsetString.equalsIgnoreCase("iso-8859-16") || - charsetString.equalsIgnoreCase("iso-8859-2") || - charsetString.equalsIgnoreCase("iso-8859-3") || - charsetString.equalsIgnoreCase("iso-8859-4") || - charsetString.equalsIgnoreCase("iso-8859-5") || - charsetString.equalsIgnoreCase("iso-8859-6") || - charsetString.equalsIgnoreCase("iso-8859-7") || - charsetString.equalsIgnoreCase("iso-8859-8") || - charsetString.equalsIgnoreCase("iso-8859-8-i") || - charsetString.equalsIgnoreCase("macintosh") || - charsetString.equalsIgnoreCase("shift_jis") || - charsetString.equalsIgnoreCase("windows-1250") || - charsetString.equalsIgnoreCase("windows-1251") || - charsetString.equalsIgnoreCase("windows-1252") || - charsetString.equalsIgnoreCase("windows-1253") || - charsetString.equalsIgnoreCase("windows-1254") || - charsetString.equalsIgnoreCase("windows-1255") || - charsetString.equalsIgnoreCase("windows-1256") || - charsetString.equalsIgnoreCase("windows-1257") || - charsetString.equalsIgnoreCase("windows-1258") || - charsetString.equalsIgnoreCase("windows-874") || - charsetString.equalsIgnoreCase("x-mac-cyrillic") || - charsetString.equalsIgnoreCase("x-user-defined"); - } +package io.rudefox.burrow; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +class QRCode { + + private static final String FALLBACK_SET_STRING = "##"; + + static String toQRCode(String data) { + + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + Map hints = new HashMap<>(); + hints.put(EncodeHintType.MARGIN, 0); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); + + BitMatrix bitMatrix = null; + try { + bitMatrix = qrCodeWriter.encode(data, BarcodeFormat.QR_CODE, 0, 0, hints); + } catch (WriterException e) { + e.printStackTrace(); + } + + if (bitMatrix == null) + return "[Error Generating QR Code]"; + + if (charsetDoesNotSupportFullBlockChar()) + return bitMatrix.toString(FALLBACK_SET_STRING, " "); + + return toUTF8(bitMatrix); + } + + private static String toUTF8(BitMatrix bitMatrix) { + + char SPACE = '\u0020'; + char LOWER_BLOCK = '\u2584'; + char UPPER_BLOCK = '\u2580'; + char FULL_BLOCK = '\u2588'; + + int[] blockCodepoints = new int[] {SPACE, LOWER_BLOCK, UPPER_BLOCK, FULL_BLOCK}; + + StringBuilder qrCode = new StringBuilder(); + + for (int y = 0; y < bitMatrix.getHeight(); y += 2) { + + for (int x = 0; x < bitMatrix.getWidth(); x++) { + + int upper_bit = bitMatrix.get(x, y) ? 1 : 0; + int lower_bit = y+1 < bitMatrix.getHeight() && bitMatrix.get(x, y+1) ? 1 : 0; + + int index = upper_bit << 1 | lower_bit; + int blockCodepoint = blockCodepoints[index]; + + qrCode.appendCodePoint(blockCodepoint); + } + + if (y < bitMatrix.getHeight() - 1) + qrCode.append("\n"); + } + + return qrCode.toString(); + } + + private static boolean charsetDoesNotSupportFullBlockChar() { + + Charset charset = ServiceLocator.DEFAULT_CHARSET; + String charsetString = charset.toString(); + + return charset.equals(StandardCharsets.ISO_8859_1) || + charset.equals(StandardCharsets.US_ASCII) || + charsetString.equalsIgnoreCase("euc-jp") || + charsetString.equalsIgnoreCase("euc-kr") || + charsetString.equalsIgnoreCase("iso-2022-jp") || + charsetString.equalsIgnoreCase("iso-8859-10") || + charsetString.equalsIgnoreCase("iso-8859-13") || + charsetString.equalsIgnoreCase("iso-8859-14") || + charsetString.equalsIgnoreCase("iso-8859-15") || + charsetString.equalsIgnoreCase("iso-8859-16") || + charsetString.equalsIgnoreCase("iso-8859-2") || + charsetString.equalsIgnoreCase("iso-8859-3") || + charsetString.equalsIgnoreCase("iso-8859-4") || + charsetString.equalsIgnoreCase("iso-8859-5") || + charsetString.equalsIgnoreCase("iso-8859-6") || + charsetString.equalsIgnoreCase("iso-8859-7") || + charsetString.equalsIgnoreCase("iso-8859-8") || + charsetString.equalsIgnoreCase("iso-8859-8-i") || + charsetString.equalsIgnoreCase("macintosh") || + charsetString.equalsIgnoreCase("shift_jis") || + charsetString.equalsIgnoreCase("windows-1250") || + charsetString.equalsIgnoreCase("windows-1251") || + charsetString.equalsIgnoreCase("windows-1252") || + charsetString.equalsIgnoreCase("windows-1253") || + charsetString.equalsIgnoreCase("windows-1254") || + charsetString.equalsIgnoreCase("windows-1255") || + charsetString.equalsIgnoreCase("windows-1256") || + charsetString.equalsIgnoreCase("windows-1257") || + charsetString.equalsIgnoreCase("windows-1258") || + charsetString.equalsIgnoreCase("windows-874") || + charsetString.equalsIgnoreCase("x-mac-cyrillic") || + charsetString.equalsIgnoreCase("x-user-defined"); + } } \ No newline at end of file diff --git a/src/main/java/io/rudefox/cold/RudefoxCold.java b/src/main/java/io/rudefox/burrow/RudefoxBurrow.java similarity index 81% rename from src/main/java/io/rudefox/cold/RudefoxCold.java rename to src/main/java/io/rudefox/burrow/RudefoxBurrow.java index 402e9fa..f3e286d 100644 --- a/src/main/java/io/rudefox/cold/RudefoxCold.java +++ b/src/main/java/io/rudefox/burrow/RudefoxBurrow.java @@ -1,32 +1,32 @@ -package io.rudefox.cold; - -import com.bjdweck.bitcoin.params.INetworkParameters; -import com.bjdweck.bitcoin.params.NetworkParameters; -import picocli.CommandLine; - -@CommandLine.Command( - name = "rudefox-cold", - synopsisSubcommandLabel = "COMMAND", - description = "Offline wallet tool", - subcommands = {MnemonicCommand.class, WalletCommand.class} -) -public class RudefoxCold implements Runnable { - - @CommandLine.Option(names = "--testnet", description = "run on Bitcoin Testnet (default: Mainnet)") - boolean testnet = false; - - public INetworkParameters getNetworkParameters() { - return testnet ? NetworkParameters.Testnet() : NetworkParameters.Mainnet(); - } - - public static void main(String[] args) { - new CommandLine(new RudefoxCold()).execute(args); - } - - @CommandLine.Spec - CommandLine.Model.CommandSpec commandSpec; - - public void run() { - throw new CommandLine.ParameterException(commandSpec.commandLine(), "Missing required subcommand"); - } +package io.rudefox.burrow; + +import com.bjdweck.bitcoin.params.INetworkParameters; +import com.bjdweck.bitcoin.params.NetworkParameters; +import picocli.CommandLine; + +@CommandLine.Command( + name = "burrow", + synopsisSubcommandLabel = "COMMAND", + description = "Offline wallet tool", + subcommands = {MnemonicCommand.class, WalletCommand.class} +) +public class RudefoxBurrow implements Runnable { + + @CommandLine.Option(names = "--testnet", description = "run on Bitcoin Testnet (default: Mainnet)") + boolean testnet = false; + + public INetworkParameters getNetworkParameters() { + return testnet ? NetworkParameters.Testnet() : NetworkParameters.Mainnet(); + } + + public static void main(String[] args) { + new CommandLine(new RudefoxBurrow()).execute(args); + } + + @CommandLine.Spec + CommandLine.Model.CommandSpec commandSpec; + + public void run() { + throw new CommandLine.ParameterException(commandSpec.commandLine(), "Missing required subcommand"); + } } \ No newline at end of file diff --git a/src/main/java/io/rudefox/cold/ServiceLocator.java b/src/main/java/io/rudefox/burrow/ServiceLocator.java similarity index 80% rename from src/main/java/io/rudefox/cold/ServiceLocator.java rename to src/main/java/io/rudefox/burrow/ServiceLocator.java index 7167f24..a6ad434 100644 --- a/src/main/java/io/rudefox/cold/ServiceLocator.java +++ b/src/main/java/io/rudefox/burrow/ServiceLocator.java @@ -1,8 +1,8 @@ -package io.rudefox.cold; - -import java.nio.charset.Charset; - -public class ServiceLocator { - - public static Charset DEFAULT_CHARSET = Charset.defaultCharset(); -} +package io.rudefox.burrow; + +import java.nio.charset.Charset; + +public class ServiceLocator { + + public static Charset DEFAULT_CHARSET = Charset.defaultCharset(); +} diff --git a/src/main/java/io/rudefox/cold/WalletCommand.java b/src/main/java/io/rudefox/burrow/WalletCommand.java similarity index 96% rename from src/main/java/io/rudefox/cold/WalletCommand.java rename to src/main/java/io/rudefox/burrow/WalletCommand.java index 447fb6b..fce096f 100644 --- a/src/main/java/io/rudefox/cold/WalletCommand.java +++ b/src/main/java/io/rudefox/burrow/WalletCommand.java @@ -1,137 +1,137 @@ -package io.rudefox.cold; - -import com.bjdweck.bitcoin.address.BitcoinAddress; -import com.bjdweck.bitcoin.extendedkey.AddressChain; -import com.bjdweck.bitcoin.extendedkey.DerivationPath; -import com.bjdweck.bitcoin.extendedkey.KeyOrigin; -import com.bjdweck.bitcoin.extendedkey.PrivateExtendedKey; -import com.bjdweck.bitcoin.hdwallet.Bip44Purpose; -import com.bjdweck.bitcoin.hdwallet.Bip44SigningAccount; -import com.bjdweck.bitcoin.hdwallet.Bip44Wallet; -import com.bjdweck.bitcoin.mnemonic.Mnemonic; -import com.bjdweck.bitcoin.mnemonic.Seed; -import com.bjdweck.bitcoin.psbt.Psbt; -import com.bjdweck.bitcoin.transaction.scriptpubkey.ScriptPubKey; -import picocli.CommandLine; - -@SuppressWarnings("ALL") -@CommandLine.Command(name = "wallet", description = "BIP44 wallet operations") -public class WalletCommand implements Runnable { - - @CommandLine.ParentCommand - private RudefoxCold globalOptions; - - @CommandLine.Option(names = {"-s", "--sentence"}, description = "mnemonic sentence", required = true) - String sentence; - - @CommandLine.Option(names = {"-p", "--passphrase"}, description = "optional mnemonic passphrase") - String passphrase; - - Bip44Wallet getWallet() { - Seed seed = Mnemonic.fromSentence(sentence).generateSeed(passphrase); - return new Bip44Wallet(PrivateExtendedKey.fromSeed(seed), globalOptions.getNetworkParameters()); - } - - Bip44SigningAccount getAccount(Bip44Purpose purpose, int accountSeq) { - return getWallet().deriveAccount(purpose, accountSeq); - } - - @CommandLine.Command(name = "listxpub", description = "list xpub (default: SLIP-0132)") - public int listXpub( - - @CommandLine.Option(names = {"-q", "--qrcode"}, description = "output QR code") - boolean isQRCode, - - @CommandLine.Option(names = {"-p", "--purpose"}, description = "purpose", - paramLabel = "[P2PKH, P2SH_P2WPKH, P2WPKH, P2SH_MultiSigP2WSH, MultiSigP2WSH]", - defaultValue = "P2PKH") - Bip44Purpose purpose, - - @CommandLine.Option(names = {"-a", "--account"}, description = "account sequence number", - paramLabel = "n", defaultValue = "0") - int accountSeq) { - - Bip44SigningAccount bip44SigningAccount = getAccount(purpose, accountSeq); - String slip0132 = bip44SigningAccount.toXpubSlip0132Base58(); - String keyOrigin = bip44SigningAccount.getKeyOrigin().toString(); - - if (isQRCode) { - System.out.println(QRCode.toQRCode(slip0132)); - return 0; - } - - System.out.printf("[%s]%s%n", keyOrigin, slip0132); - return 0; - } - - @CommandLine.Command(name = "listaddress", description = "list account addresses") - public int listAddress( - - @CommandLine.Option(names = {"-p", "--purpose"}, description = "purpose", - paramLabel = "[P2PKH, P2SH_P2WPKH, P2WPKH, P2SH_MultiSigP2WSH, MultiSigP2WSH]", - defaultValue = "P2PKH") - Bip44Purpose purpose, - - @CommandLine.Option(names = {"-a", "--account"}, description = "account sequence number", - paramLabel = "", defaultValue = "0") - int accountSeq, - - @CommandLine.Option(names = {"-c", "--address-chain"}, description = "address chain", - paramLabel = "[INTERNAL|EXTERNAL]", defaultValue = "EXTERNAL") - AddressChain addressChain, - - @CommandLine.Option(names = {"-s", "--address-seq"}, description = "starting address sequence number", - paramLabel = "", defaultValue = "0") - int addressSeq, - - @CommandLine.Option(names = {"-n", "--count"}, description = "number of addresses to list", - paramLabel = "", defaultValue = "10") - int count) { - - Bip44SigningAccount account = getAccount(purpose, accountSeq); - - for (int i = 0; i < count; i++) { - - int seq = addressSeq + i; - - DerivationPath addressPath = DerivationPath.m().slash(addressChain.getSequence()).slash(seq); - KeyOrigin keyOrigin = account.getKeyOrigin().combineRelativePath(addressPath); - - ScriptPubKey scriptPubKey = account.deriveScriptPubKey(addressChain, seq); - BitcoinAddress address = scriptPubKey.getAddress(globalOptions.getNetworkParameters()); - - System.out.printf("%s: %s%n", keyOrigin, address); - } - - return 0; - } - - @CommandLine.Command(name = "signpsbt", description = "sign a PSBT") - public int signPsbt( - @CommandLine.Parameters() - String psbtBase64) { - - Psbt psbt = Psbt.fromBase64(psbtBase64); - - psbt.getValidationViolationMap(); - /* - Satoshi fee = psbt.getFee(); - Satoshi spendAmount = psbt.getSpendAmount(getWallet()); - BitcoinAddress spendAddress = psbt.getSpendAddress(getWallet()); - - System.out.printf("Confirm spend of %s to %s with a fee of %s", spendAmount.toBitcoinString(), spendAddress.getAddressString(), fee.toBitcoinString()); -*/ - psbt.signPsbt(getWallet()); - - System.out.println(psbt.toBase64()); - - return 0; - } - - @CommandLine.Spec - CommandLine.Model.CommandSpec commandSpec; - - public void run() { - throw new CommandLine.ParameterException(commandSpec.commandLine(), "Missing required wallet subcommand"); - } +package io.rudefox.burrow; + +import com.bjdweck.bitcoin.address.BitcoinAddress; +import com.bjdweck.bitcoin.extendedkey.AddressChain; +import com.bjdweck.bitcoin.extendedkey.DerivationPath; +import com.bjdweck.bitcoin.extendedkey.KeyOrigin; +import com.bjdweck.bitcoin.extendedkey.PrivateExtendedKey; +import com.bjdweck.bitcoin.hdwallet.Bip44Purpose; +import com.bjdweck.bitcoin.hdwallet.Bip44SigningAccount; +import com.bjdweck.bitcoin.hdwallet.Bip44Wallet; +import com.bjdweck.bitcoin.mnemonic.Mnemonic; +import com.bjdweck.bitcoin.mnemonic.Seed; +import com.bjdweck.bitcoin.psbt.Psbt; +import com.bjdweck.bitcoin.transaction.scriptpubkey.ScriptPubKey; +import picocli.CommandLine; + +@SuppressWarnings("ALL") +@CommandLine.Command(name = "wallet", description = "BIP44 wallet operations") +public class WalletCommand implements Runnable { + + @CommandLine.ParentCommand + private RudefoxBurrow globalOptions; + + @CommandLine.Option(names = {"-s", "--sentence"}, description = "mnemonic sentence", required = true) + String sentence; + + @CommandLine.Option(names = {"-p", "--passphrase"}, description = "optional mnemonic passphrase") + String passphrase; + + Bip44Wallet getWallet() { + Seed seed = Mnemonic.fromSentence(sentence).generateSeed(passphrase); + return new Bip44Wallet(PrivateExtendedKey.fromSeed(seed), globalOptions.getNetworkParameters()); + } + + Bip44SigningAccount getAccount(Bip44Purpose purpose, int accountSeq) { + return getWallet().deriveAccount(purpose, accountSeq); + } + + @CommandLine.Command(name = "listxpub", description = "list xpub (default: SLIP-0132)") + public int listXpub( + + @CommandLine.Option(names = {"-q", "--qrcode"}, description = "output QR code") + boolean isQRCode, + + @CommandLine.Option(names = {"-p", "--purpose"}, description = "purpose", + paramLabel = "[P2PKH, P2SH_P2WPKH, P2WPKH, P2SH_MultiSigP2WSH, MultiSigP2WSH]", + defaultValue = "P2PKH") + Bip44Purpose purpose, + + @CommandLine.Option(names = {"-a", "--account"}, description = "account sequence number", + paramLabel = "n", defaultValue = "0") + int accountSeq) { + + Bip44SigningAccount bip44SigningAccount = getAccount(purpose, accountSeq); + String slip0132 = bip44SigningAccount.toXpubSlip0132Base58(); + String keyOrigin = bip44SigningAccount.getKeyOrigin().toString(); + + if (isQRCode) { + System.out.println(QRCode.toQRCode(slip0132)); + return 0; + } + + System.out.printf("[%s]%s%n", keyOrigin, slip0132); + return 0; + } + + @CommandLine.Command(name = "listaddress", description = "list account addresses") + public int listAddress( + + @CommandLine.Option(names = {"-p", "--purpose"}, description = "purpose", + paramLabel = "[P2PKH, P2SH_P2WPKH, P2WPKH, P2SH_MultiSigP2WSH, MultiSigP2WSH]", + defaultValue = "P2PKH") + Bip44Purpose purpose, + + @CommandLine.Option(names = {"-a", "--account"}, description = "account sequence number", + paramLabel = "", defaultValue = "0") + int accountSeq, + + @CommandLine.Option(names = {"-c", "--address-chain"}, description = "address chain", + paramLabel = "[INTERNAL|EXTERNAL]", defaultValue = "EXTERNAL") + AddressChain addressChain, + + @CommandLine.Option(names = {"-s", "--address-seq"}, description = "starting address sequence number", + paramLabel = "", defaultValue = "0") + int addressSeq, + + @CommandLine.Option(names = {"-n", "--count"}, description = "number of addresses to list", + paramLabel = "", defaultValue = "10") + int count) { + + Bip44SigningAccount account = getAccount(purpose, accountSeq); + + for (int i = 0; i < count; i++) { + + int seq = addressSeq + i; + + DerivationPath addressPath = DerivationPath.m().slash(addressChain.getSequence()).slash(seq); + KeyOrigin keyOrigin = account.getKeyOrigin().combineRelativePath(addressPath); + + ScriptPubKey scriptPubKey = account.deriveScriptPubKey(addressChain, seq); + BitcoinAddress address = scriptPubKey.getAddress(globalOptions.getNetworkParameters()); + + System.out.printf("%s: %s%n", keyOrigin, address); + } + + return 0; + } + + @CommandLine.Command(name = "signpsbt", description = "sign a PSBT") + public int signPsbt( + @CommandLine.Parameters() + String psbtBase64) { + + Psbt psbt = Psbt.fromBase64(psbtBase64); + + psbt.getValidationViolationMap(); + /* + Satoshi fee = psbt.getFee(); + Satoshi spendAmount = psbt.getSpendAmount(getWallet()); + BitcoinAddress spendAddress = psbt.getSpendAddress(getWallet()); + + System.out.printf("Confirm spend of %s to %s with a fee of %s", spendAmount.toBitcoinString(), spendAddress.getAddressString(), fee.toBitcoinString()); +*/ + psbt.signPsbt(getWallet()); + + System.out.println(psbt.toBase64()); + + return 0; + } + + @CommandLine.Spec + CommandLine.Model.CommandSpec commandSpec; + + public void run() { + throw new CommandLine.ParameterException(commandSpec.commandLine(), "Missing required wallet subcommand"); + } } \ No newline at end of file diff --git a/src/test/java/io/rudefox/cold/mnemonic_8_sided_dice_tests.java b/src/test/java/io/rudefox/burrow/mnemonic_8_sided_dice_tests.java similarity index 94% rename from src/test/java/io/rudefox/cold/mnemonic_8_sided_dice_tests.java rename to src/test/java/io/rudefox/burrow/mnemonic_8_sided_dice_tests.java index a252b36..97f2665 100644 --- a/src/test/java/io/rudefox/cold/mnemonic_8_sided_dice_tests.java +++ b/src/test/java/io/rudefox/burrow/mnemonic_8_sided_dice_tests.java @@ -1,64 +1,64 @@ -package io.rudefox.cold; - -import com.bjdweck.test.CliTestFixture; -import org.junit.jupiter.api.Test; - -import java.io.UnsupportedEncodingException; - -class mnemonic_8_sided_dice_tests extends CliTestFixture { - - @Test - void with_arguments_interactive_8_sided_dice_should_generate_mnemonic_sentence() throws UnsupportedEncodingException { - - withArgs("mnemonic -i8 --bits 128"); - - expectedOutput("Input 11 x 8-sided dice rolls [1-8]: "); - provideInput("12345678123" + EOL); - - expectedOutput(EOL); - expectedOutput("| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 1 | 2 | 3 |" + EOL); - expectedOutput("|000 001 010 01 | 1 100 101 110 1 | 11 000 001 010|" + EOL); - expectedOutput("| 1. ahead | 2. slight | 3. scout |" + EOL); - expectedOutput(EOL); - - expectedOutput("Input 11 x 8-sided dice rolls [1-8]: "); - provideInput("12345678123" + EOL); - - expectedOutput(EOL); - expectedOutput("| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 1 | 2 | 3 |" + EOL); - expectedOutput("|000 001 010 01 | 1 100 101 110 1 | 11 000 001 010|" + EOL); - expectedOutput("| 4. ahead | 5. slight | 6. scout |" + EOL); - expectedOutput(EOL); - - expectedOutput("Input 11 x 8-sided dice rolls [1-8]: "); - provideInput("12345678123" + EOL); - - expectedOutput(EOL); - expectedOutput("| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 1 | 2 | 3 |" + EOL); - expectedOutput("|000 001 010 01 | 1 100 101 110 1 | 11 000 001 010|" + EOL); - expectedOutput("| 7. ahead | 8. slight | 9. scout |" + EOL); - expectedOutput(EOL); - - expectedOutput("Input 11 x 8-sided dice rolls [1-8]: "); - provideInput("12345678123" + EOL); - - expectedOutput(EOL); - expectedOutput("| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 1 | 2 | 3 |" + EOL); - expectedOutput("|000 001 010 01 | 1 100 101 110 1 | 11 000 00- ---|" + EOL); - expectedOutput("| 10. ahead | 11. slight | 12. CHECKWORD |" + EOL); - expectedOutput(EOL); - - expectedOutput("ahead slight scout ahead slight scout ahead slight scout ahead slight scan" + EOL); - - doMain(); - - verifyOutput(); - } - - private void doMain() { - - setInput(); - - RudefoxCold.main(getArgs()); - } +package io.rudefox.burrow; + +import com.bjdweck.test.CliTestFixture; +import org.junit.jupiter.api.Test; + +import java.io.UnsupportedEncodingException; + +class mnemonic_8_sided_dice_tests extends CliTestFixture { + + @Test + void with_arguments_interactive_8_sided_dice_should_generate_mnemonic_sentence() throws UnsupportedEncodingException { + + withArgs("mnemonic -i8 --bits 128"); + + expectedOutput("Input 11 x 8-sided dice rolls [1-8]: "); + provideInput("12345678123" + EOL); + + expectedOutput(EOL); + expectedOutput("| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 1 | 2 | 3 |" + EOL); + expectedOutput("|000 001 010 01 | 1 100 101 110 1 | 11 000 001 010|" + EOL); + expectedOutput("| 1. ahead | 2. slight | 3. scout |" + EOL); + expectedOutput(EOL); + + expectedOutput("Input 11 x 8-sided dice rolls [1-8]: "); + provideInput("12345678123" + EOL); + + expectedOutput(EOL); + expectedOutput("| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 1 | 2 | 3 |" + EOL); + expectedOutput("|000 001 010 01 | 1 100 101 110 1 | 11 000 001 010|" + EOL); + expectedOutput("| 4. ahead | 5. slight | 6. scout |" + EOL); + expectedOutput(EOL); + + expectedOutput("Input 11 x 8-sided dice rolls [1-8]: "); + provideInput("12345678123" + EOL); + + expectedOutput(EOL); + expectedOutput("| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 1 | 2 | 3 |" + EOL); + expectedOutput("|000 001 010 01 | 1 100 101 110 1 | 11 000 001 010|" + EOL); + expectedOutput("| 7. ahead | 8. slight | 9. scout |" + EOL); + expectedOutput(EOL); + + expectedOutput("Input 11 x 8-sided dice rolls [1-8]: "); + provideInput("12345678123" + EOL); + + expectedOutput(EOL); + expectedOutput("| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 1 | 2 | 3 |" + EOL); + expectedOutput("|000 001 010 01 | 1 100 101 110 1 | 11 000 00- ---|" + EOL); + expectedOutput("| 10. ahead | 11. slight | 12. CHECKWORD |" + EOL); + expectedOutput(EOL); + + expectedOutput("ahead slight scout ahead slight scout ahead slight scout ahead slight scan" + EOL); + + doMain(); + + verifyOutput(); + } + + private void doMain() { + + setInput(); + + RudefoxBurrow.main(getArgs()); + } } \ No newline at end of file diff --git a/src/test/java/io/rudefox/cold/mnemonic_command_tests.java b/src/test/java/io/rudefox/burrow/mnemonic_command_tests.java similarity index 93% rename from src/test/java/io/rudefox/cold/mnemonic_command_tests.java rename to src/test/java/io/rudefox/burrow/mnemonic_command_tests.java index dd10a47..e6457d0 100644 --- a/src/test/java/io/rudefox/cold/mnemonic_command_tests.java +++ b/src/test/java/io/rudefox/burrow/mnemonic_command_tests.java @@ -1,71 +1,71 @@ -package io.rudefox.cold; - -import com.bjdweck.test.CliTestFixture; -import org.junit.jupiter.api.Test; - -import java.io.UnsupportedEncodingException; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class mnemonic_command_tests extends CliTestFixture { - - @Test - void with_arguments_interactive_dice_should_generate_mnemonic_sentence() throws UnsupportedEncodingException { - - withArgs("mnemonic --interactive --dice6-entropy --bits 128"); - - expectedOutput("Input 50 dice rolls [1-6]: "); - provideInput("234322343242422344161254151\r\n"); - - expectedOutput("Input 23 more dice rolls [1-6]: "); - provideInput("33116265515343114314456\r\n"); - - expectedOutput("mountain tilt wing silk rude fox almost volume wine media verify card" + EOL); - - doMain(); - - verifyOutput(); - } - - @Test - void with_no_arguments_should_output_mnemonic() throws UnsupportedEncodingException { - - withArgs("mnemonic"); - - doMain(); - - assertEquals(24, getOutput().split(" ").length); - } - - @Test - void with_arguments_non_interactive_dice_bits_should_generate_mnemonic_sentence() throws UnsupportedEncodingException { - - withArgs("mnemonic --dice6-entropy --events 23432234324242234416125415133116265515343114314456 --bits 128"); - - expectedOutput("mountain tilt wing silk rude fox almost volume wine media verify card" + EOL); - - doMain(); - - verifyOutput(); - } - - @Test - void with_arguments_without_interactive_dice_should_generate_mnemonic_sentence() throws UnsupportedEncodingException { - - withArgs("mnemonic --dice6-entropy --events 2343223432424223441612541513311626551534311431445623432234324242234416125415133116265515343114314456"); - - expectedOutput("first welcome social broccoli nasty rather weird uncle spirit horn update pencil help rescue " + - "grape enough fork wave eight fuel ribbon pony clean couple" + EOL); - - doMain(); - - verifyOutput(); - } - - private void doMain() { - - setInput(); - - RudefoxCold.main(getArgs()); - } +package io.rudefox.burrow; + +import com.bjdweck.test.CliTestFixture; +import org.junit.jupiter.api.Test; + +import java.io.UnsupportedEncodingException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class mnemonic_command_tests extends CliTestFixture { + + @Test + void with_arguments_interactive_dice_should_generate_mnemonic_sentence() throws UnsupportedEncodingException { + + withArgs("mnemonic --interactive --dice6-entropy --bits 128"); + + expectedOutput("Input 50 dice rolls [1-6]: "); + provideInput("234322343242422344161254151\r\n"); + + expectedOutput("Input 23 more dice rolls [1-6]: "); + provideInput("33116265515343114314456\r\n"); + + expectedOutput("mountain tilt wing silk rude fox almost volume wine media verify card" + EOL); + + doMain(); + + verifyOutput(); + } + + @Test + void with_no_arguments_should_output_mnemonic() throws UnsupportedEncodingException { + + withArgs("mnemonic"); + + doMain(); + + assertEquals(24, getOutput().split(" ").length); + } + + @Test + void with_arguments_non_interactive_dice_bits_should_generate_mnemonic_sentence() throws UnsupportedEncodingException { + + withArgs("mnemonic --dice6-entropy --events 23432234324242234416125415133116265515343114314456 --bits 128"); + + expectedOutput("mountain tilt wing silk rude fox almost volume wine media verify card" + EOL); + + doMain(); + + verifyOutput(); + } + + @Test + void with_arguments_without_interactive_dice_should_generate_mnemonic_sentence() throws UnsupportedEncodingException { + + withArgs("mnemonic --dice6-entropy --events 2343223432424223441612541513311626551534311431445623432234324242234416125415133116265515343114314456"); + + expectedOutput("first welcome social broccoli nasty rather weird uncle spirit horn update pencil help rescue " + + "grape enough fork wave eight fuel ribbon pony clean couple" + EOL); + + doMain(); + + verifyOutput(); + } + + private void doMain() { + + setInput(); + + RudefoxBurrow.main(getArgs()); + } } \ No newline at end of file diff --git a/src/test/java/io/rudefox/cold/wallet_command_tests.java b/src/test/java/io/rudefox/burrow/wallet_command_tests.java similarity index 97% rename from src/test/java/io/rudefox/cold/wallet_command_tests.java rename to src/test/java/io/rudefox/burrow/wallet_command_tests.java index 5ccfdf0..f41ba3d 100644 --- a/src/test/java/io/rudefox/cold/wallet_command_tests.java +++ b/src/test/java/io/rudefox/burrow/wallet_command_tests.java @@ -1,145 +1,145 @@ -package io.rudefox.cold; - -import com.bjdweck.test.CliTestFixture; -import org.junit.jupiter.api.Test; - -import java.io.UnsupportedEncodingException; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class wallet_command_tests extends CliTestFixture { - - static { - OUTPUT_CHARSET = "UTF8"; - } - - @Test - void with_sentence_and_passphrase_should_output_xpub() throws UnsupportedEncodingException { - - withArgs(new String[] {"wallet", - "--sentence","stove prefer lunch collect small orphan wasp size beyond auction guilt great", - "--passphrase","apple", - "listxpub"}); - - doMain(); - - assertEquals("[39F8B071:m/44'/0'/0']xpub6CKy5SECeJipZid8dF3bopoMGdRzd7hMJuPzMGesZCobrMSssZyASexzXuzRTPVLcqqdyAEZJKPMGvDthgZW2Z3mPHLohxEAVbkvGKAXjqx" + EOL, getOutput()); - } - - @Test - void with_testnet_sentence_and_passphrase_and_witness_should_output_vpub() throws UnsupportedEncodingException { - - withArgs(new String[] {"--testnet", - "wallet", - "--sentence","icon issue absorb apology price atom bread toward worry final dune dial swing armor donkey", - "--passphrase","apple", - "listxpub", - "--purpose","P2WPKH", - "--account","6"}); - - doMain(); - - assertEquals("[CBACA81C:m/84'/1'/6']vpub5Z1KP7PJ8f85AqbvPMKeFHxmDfrgimiAar3KqwCzfsw65tmFLevm4MJX9cRcoiERYkjeG5Z3VwhSf1E3fVBFF86J33CHSED45Hk6kLZumqV" + EOL, getOutput()); - } - - @Test - void with_testnet_sentence_and_passphrase_and_witness_should_output_addresses() throws UnsupportedEncodingException { - - withArgs(new String[] {"--testnet", - "wallet", - "--sentence","icon issue absorb apology price atom bread toward worry final dune dial swing armor donkey", - "--passphrase","apple", - "listaddress", - "--purpose","P2WPKH", - "--account","6", - "--address-chain","INTERNAL", - "--address-seq","3", - "--count","5"}); - - doMain(); - - assertEquals("CBACA81C:m/84'/1'/6'/1/3: tb1qs7cnr9sql9nef5e95jk5t7pr2qwpjsqtffnuxj" + EOL + - "CBACA81C:m/84'/1'/6'/1/4: tb1qgl947tylg3tazgdy2rm3rs4gxheue707dkg3q2" + EOL + - "CBACA81C:m/84'/1'/6'/1/5: tb1q03n6q2jjeks9zaneglqz5g37xkcrzy8nh8hn6s" + EOL + - "CBACA81C:m/84'/1'/6'/1/6: tb1q50gylhzz2mrf76ffn7dc5zvymsd9lghj24t7ns" + EOL + - "CBACA81C:m/84'/1'/6'/1/7: tb1qvzkez0zg6xm9z780n0358xp7nx96fh508awktw" + EOL, getOutput()); - } - - @Test - void with_testnet_sentence_and_passphrase_and_witness_can_sign_psbt() throws UnsupportedEncodingException { - - withArgs(new String[] {"--testnet", - "wallet", - "--sentence","vibrant delay limit tenant prepare reflect lonely pepper dragon calm dolphin prize slide inch purse term raw eternal twin kidney scan power magnet humble", - "signpsbt", - "cHNidP8BAHcCAAAAAS/zE49gxbTp0n4mTv0wPV7KuLtQlbzTL/E9s4b4JKl/AQAAAAD/////AkAfAAAAAAAAGXapFE9U1GY" + - "KflieNl1PUIpdTTzcXnUkiKxiyR4AAAAAABl2qRR6uJziLUo4i7ooJyzxIctMZfxb5IisAAAAAE8BBDWHzwOP9tWogAAAAN/zgjOY" + - "H39q085n7GG+5+/Jtovkvc8WQftwMSsBV7svAhPpi88y7pGhXzOdH0iJbXJsMq3F9GNRnRyqkQ7hVEtjEI2C0R8sAACAAQAAgAAAA" + - "IAAAQDeAQAAAAGVcHJZzeDV1JsQv869yoS/f8eSufNKON0o56ChVEnxCwEAAABqRzBEAiAD95mxgC+CHJNt1Ui+Y/raIymIiK1fyV" + - "c9wx0EALk/uQIgdQS27tjyujeItDg96m5GR7UD5tIz+95A4RQX4YOXXZoBIQNsvOVlbTqbZoKJbx8z94F6C/xS4faQ2XsHINcd50q" + - "T5P////8CQB8AAAAAAAAWABRzMqPZdHy0JMwPKY/YRhrODmyijpzpHgAAAAAAGXapFKmSAlpRxRJXr8ywpN500rXVsxIHiKwAAAAA" + - "IgYCUEaaspCm8AVu5WJ9Z7RG53iW66Btq6QODz7xfMlRogoYjYLRHywAAIABAACAAAAAgAEAAAADAAAAAAAA"}); - - doMain(); - - assertEquals("cHNidP8BAHcCAAAAAS/zE49gxbTp0n4mTv0wPV7KuLtQlbzTL/E9s4b4JKl/AQAAAAD/////AkAfAAAAAAAAGXapFE9U1GYKflieNl1PUIpdTTzcXnUkiKxiyR4AAAAAABl2qRR6uJziLUo4i7ooJyzxIctMZfxb5IisAAAAAE8BBDWHzwOP9tWogAAAAN/zgjOYH39q085n7GG+5+/Jtovkvc8WQftwMSsBV7svAhPpi88y7pGhXzOdH0iJbXJsMq3F9GNRnRyqkQ7hVEtjEI2C0R8sAACAAQAAgAAAAIAAAQDeAQAAAAGVcHJZzeDV1JsQv869yoS/f8eSufNKON0o56ChVEnxCwEAAABqRzBEAiAD95mxgC+CHJNt1Ui+Y/raIymIiK1fyVc9wx0EALk/uQIgdQS27tjyujeItDg96m5GR7UD5tIz+95A4RQX4YOXXZoBIQNsvOVlbTqbZoKJbx8z94F6C/xS4faQ2XsHINcd50qT5P////8CQB8AAAAAAAAWABRzMqPZdHy0JMwPKY/YRhrODmyijpzpHgAAAAAAGXapFKmSAlpRxRJXr8ywpN500rXVsxIHiKwAAAAAIgYCUEaaspCm8AVu5WJ9Z7RG53iW66Btq6QODz7xfMlRogoYjYLRHywAAIABAACAAAAAgAEAAAADAAAAIgICUEaaspCm8AVu5WJ9Z7RG53iW66Btq6QODz7xfMlRogpIMEUCIQCrgKRLUmzYL8edfZ7cltQWnLdVdU2Ta3BYbu5NR8JYwgIgDeOEdkOmOkPp7HpC4dkniXvXsvfzagmJ6pkBDd2DcgwBAAAA" + EOL, getOutput()); - } - - @Test - void with_sentence_without_passphrase_should_output_xpub() throws UnsupportedEncodingException { - - withArgs(new String[] {"wallet", - "--sentence","ordinary debate stomach mix poverty upset amateur small sadness female general fabric", - "listxpub"}); - - doMain(); - - assertEquals("[A6932DE1:m/44'/0'/0']xpub6BjcsZDafUukJNMhD1Porw4SZB1RkiCE7EsQWJGVaMcZd9qXyawcjeVa28SJt5WNNAKJGGUbebfgzyrsWPTsLRkWmMNwrThaq8umcp7Yzn8" + EOL, getOutput()); - } - - @Test - void with_sentence_without_passphrase_should_output_xpub_qrcode() throws UnsupportedEncodingException { - - withArgs(new String[] {"wallet", - "--sentence","ordinary debate stomach mix poverty upset amateur small sadness female general fabric", - "listxpub", - "--qrcode"}); - - String ExpectedQRCode = - "█▀▀▀▀▀█ █ ▄ ██▀▄▄ ▀▀ ▀ █ ██▄ █▀▀▀▀▀█\n" + - "█ ███ █ █▄ ▄▀▄█ ▄▄█▄▀███▄█▄▀█▄██ █ ███ █\n" + - "█ ▀▀▀ █ ▄▄▄█ ▀▀ ▄ ▀█▀▄▀▀ ▄▀▀▀ █ █ ▀▀▀ █\n" + - "▀▀▀▀▀▀▀ ▀ ▀ ▀ ▀▄█▄▀ ▀▄▀ ▀ ▀ ▀▄█▄█ ▀▀▀▀▀▀▀\n" + - "▀▀▄ ▀▀▀▄ ▀ ▀█▄▄▄▄▀▀█ █ ▀█▄▀█▀▄█▀ █▄▀█▀▀\n" + - "▀▄ ▀█ ▀▄▄▀ █ ▄▀██▄▄▀▄ ▀█▀ █▀█▀▄ █▀███ █\n" + - "███▄▀▀▀▄█ █▀▀█▀▀▄▄▀█▀ █▄▀█ ▄█ ▄ ▀▄▄▄█ ▄▄\n" + - " ▀ ▄▀▄█▄▄ ▄ ▄ ▀█▄█▄█▀██▀ █ █ ▄▀▄█ █▀▄█\n" + - " ▀▄ ▄█▀█▀ ▀▀ ▀█▄█ ▀▄██▄▄▀█▀ ▄█▀▀▄████ \n" + - "▄ ▄▀█▄▀ █████▀▀▄ ▀ ██▀██ ▀▀ ▄▄▄▀▄▀▄▀ ▀▄▀\n" + - " █▄▄ ▀▀█▄█ ▄█▄ ▀ ▄▄▄▀█▄▀▄▄ ▄ ▄ ▀▄▄▄▄ ██\n" + - "▀▀█▀▀▄▀ ▄█▀▀ █▄█▄ ▀▄ ██ ▄█ █▀▄ ▀██▀▄▀▄█\n" + - "▀▄█ ▄▀ ▄ █▄▄▄▀▄▄ █▀▄▀ ▀▀ ███▀▀▀▀▄█▄▀█ ▀\n" + - "▀ ▄█ ▀▀ ▄▀ █▄ ▄▀ █▄ ▀▄█▀▀ ▀██▀ █ ▀▄▀▄▀▄▀\n" + - "█▀█▄▀▀▀ ██▄ ▀▀▀ ▄▄▀█▀█▀▄ ▄▄▄▄ ▀ █▄▄▄▀███\n" + - " ▀██▀▀▀█▀ ▄ ▀█ █▄▄██ ▀ ▀ █▄ ▀▄▀ █▀ ▀\n" + - "▀▀ ▀▀▄▀▀█ ▀ ███ ▀▄▀▀▀▄▄▄█ █▀▀▀███▀▀\n" + - "█▀▀▀▀▀█ ▄█▀▀ █▀█▄ ▀▄▄▄▄█▀ ▄▀ ▄ ██ ▀ █▄▀ ▀\n" + - "█ ███ █ ▀▀█▀▄█▄ ▀▄▄██▀▄█▄ ▀█▀▄▀██▀▀▀▀▄▀▄\n" + - "█ ▀▀▀ █ ▄█ █▀▀▄█▄█▄ ▄ █▀▀▄▀▄█ █▄▄▀▄▄█▀▄█\n" + - "▀▀▀▀▀▀▀ ▀ ▀ ▀▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀▀ " + EOL; - - doMain(); - - assertEquals(ExpectedQRCode, getOutput()); - } - - private void doMain() { - - setInput(); - - ServiceLocator.DEFAULT_CHARSET = StandardCharsets.UTF_8; - - RudefoxCold.main(getArgs()); - } +package io.rudefox.burrow; + +import com.bjdweck.test.CliTestFixture; +import org.junit.jupiter.api.Test; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class wallet_command_tests extends CliTestFixture { + + static { + OUTPUT_CHARSET = "UTF8"; + } + + @Test + void with_sentence_and_passphrase_should_output_xpub() throws UnsupportedEncodingException { + + withArgs(new String[] {"wallet", + "--sentence","stove prefer lunch collect small orphan wasp size beyond auction guilt great", + "--passphrase","apple", + "listxpub"}); + + doMain(); + + assertEquals("[39F8B071:m/44'/0'/0']xpub6CKy5SECeJipZid8dF3bopoMGdRzd7hMJuPzMGesZCobrMSssZyASexzXuzRTPVLcqqdyAEZJKPMGvDthgZW2Z3mPHLohxEAVbkvGKAXjqx" + EOL, getOutput()); + } + + @Test + void with_testnet_sentence_and_passphrase_and_witness_should_output_vpub() throws UnsupportedEncodingException { + + withArgs(new String[] {"--testnet", + "wallet", + "--sentence","icon issue absorb apology price atom bread toward worry final dune dial swing armor donkey", + "--passphrase","apple", + "listxpub", + "--purpose","P2WPKH", + "--account","6"}); + + doMain(); + + assertEquals("[CBACA81C:m/84'/1'/6']vpub5Z1KP7PJ8f85AqbvPMKeFHxmDfrgimiAar3KqwCzfsw65tmFLevm4MJX9cRcoiERYkjeG5Z3VwhSf1E3fVBFF86J33CHSED45Hk6kLZumqV" + EOL, getOutput()); + } + + @Test + void with_testnet_sentence_and_passphrase_and_witness_should_output_addresses() throws UnsupportedEncodingException { + + withArgs(new String[] {"--testnet", + "wallet", + "--sentence","icon issue absorb apology price atom bread toward worry final dune dial swing armor donkey", + "--passphrase","apple", + "listaddress", + "--purpose","P2WPKH", + "--account","6", + "--address-chain","INTERNAL", + "--address-seq","3", + "--count","5"}); + + doMain(); + + assertEquals("CBACA81C:m/84'/1'/6'/1/3: tb1qs7cnr9sql9nef5e95jk5t7pr2qwpjsqtffnuxj" + EOL + + "CBACA81C:m/84'/1'/6'/1/4: tb1qgl947tylg3tazgdy2rm3rs4gxheue707dkg3q2" + EOL + + "CBACA81C:m/84'/1'/6'/1/5: tb1q03n6q2jjeks9zaneglqz5g37xkcrzy8nh8hn6s" + EOL + + "CBACA81C:m/84'/1'/6'/1/6: tb1q50gylhzz2mrf76ffn7dc5zvymsd9lghj24t7ns" + EOL + + "CBACA81C:m/84'/1'/6'/1/7: tb1qvzkez0zg6xm9z780n0358xp7nx96fh508awktw" + EOL, getOutput()); + } + + @Test + void with_testnet_sentence_and_passphrase_and_witness_can_sign_psbt() throws UnsupportedEncodingException { + + withArgs(new String[] {"--testnet", + "wallet", + "--sentence","vibrant delay limit tenant prepare reflect lonely pepper dragon calm dolphin prize slide inch purse term raw eternal twin kidney scan power magnet humble", + "signpsbt", + "cHNidP8BAHcCAAAAAS/zE49gxbTp0n4mTv0wPV7KuLtQlbzTL/E9s4b4JKl/AQAAAAD/////AkAfAAAAAAAAGXapFE9U1GY" + + "KflieNl1PUIpdTTzcXnUkiKxiyR4AAAAAABl2qRR6uJziLUo4i7ooJyzxIctMZfxb5IisAAAAAE8BBDWHzwOP9tWogAAAAN/zgjOY" + + "H39q085n7GG+5+/Jtovkvc8WQftwMSsBV7svAhPpi88y7pGhXzOdH0iJbXJsMq3F9GNRnRyqkQ7hVEtjEI2C0R8sAACAAQAAgAAAA" + + "IAAAQDeAQAAAAGVcHJZzeDV1JsQv869yoS/f8eSufNKON0o56ChVEnxCwEAAABqRzBEAiAD95mxgC+CHJNt1Ui+Y/raIymIiK1fyV" + + "c9wx0EALk/uQIgdQS27tjyujeItDg96m5GR7UD5tIz+95A4RQX4YOXXZoBIQNsvOVlbTqbZoKJbx8z94F6C/xS4faQ2XsHINcd50q" + + "T5P////8CQB8AAAAAAAAWABRzMqPZdHy0JMwPKY/YRhrODmyijpzpHgAAAAAAGXapFKmSAlpRxRJXr8ywpN500rXVsxIHiKwAAAAA" + + "IgYCUEaaspCm8AVu5WJ9Z7RG53iW66Btq6QODz7xfMlRogoYjYLRHywAAIABAACAAAAAgAEAAAADAAAAAAAA"}); + + doMain(); + + assertEquals("cHNidP8BAHcCAAAAAS/zE49gxbTp0n4mTv0wPV7KuLtQlbzTL/E9s4b4JKl/AQAAAAD/////AkAfAAAAAAAAGXapFE9U1GYKflieNl1PUIpdTTzcXnUkiKxiyR4AAAAAABl2qRR6uJziLUo4i7ooJyzxIctMZfxb5IisAAAAAE8BBDWHzwOP9tWogAAAAN/zgjOYH39q085n7GG+5+/Jtovkvc8WQftwMSsBV7svAhPpi88y7pGhXzOdH0iJbXJsMq3F9GNRnRyqkQ7hVEtjEI2C0R8sAACAAQAAgAAAAIAAAQDeAQAAAAGVcHJZzeDV1JsQv869yoS/f8eSufNKON0o56ChVEnxCwEAAABqRzBEAiAD95mxgC+CHJNt1Ui+Y/raIymIiK1fyVc9wx0EALk/uQIgdQS27tjyujeItDg96m5GR7UD5tIz+95A4RQX4YOXXZoBIQNsvOVlbTqbZoKJbx8z94F6C/xS4faQ2XsHINcd50qT5P////8CQB8AAAAAAAAWABRzMqPZdHy0JMwPKY/YRhrODmyijpzpHgAAAAAAGXapFKmSAlpRxRJXr8ywpN500rXVsxIHiKwAAAAAIgYCUEaaspCm8AVu5WJ9Z7RG53iW66Btq6QODz7xfMlRogoYjYLRHywAAIABAACAAAAAgAEAAAADAAAAIgICUEaaspCm8AVu5WJ9Z7RG53iW66Btq6QODz7xfMlRogpIMEUCIQCrgKRLUmzYL8edfZ7cltQWnLdVdU2Ta3BYbu5NR8JYwgIgDeOEdkOmOkPp7HpC4dkniXvXsvfzagmJ6pkBDd2DcgwBAAAA" + EOL, getOutput()); + } + + @Test + void with_sentence_without_passphrase_should_output_xpub() throws UnsupportedEncodingException { + + withArgs(new String[] {"wallet", + "--sentence","ordinary debate stomach mix poverty upset amateur small sadness female general fabric", + "listxpub"}); + + doMain(); + + assertEquals("[A6932DE1:m/44'/0'/0']xpub6BjcsZDafUukJNMhD1Porw4SZB1RkiCE7EsQWJGVaMcZd9qXyawcjeVa28SJt5WNNAKJGGUbebfgzyrsWPTsLRkWmMNwrThaq8umcp7Yzn8" + EOL, getOutput()); + } + + @Test + void with_sentence_without_passphrase_should_output_xpub_qrcode() throws UnsupportedEncodingException { + + withArgs(new String[] {"wallet", + "--sentence","ordinary debate stomach mix poverty upset amateur small sadness female general fabric", + "listxpub", + "--qrcode"}); + + String ExpectedQRCode = + "█▀▀▀▀▀█ █ ▄ ██▀▄▄ ▀▀ ▀ █ ██▄ █▀▀▀▀▀█\n" + + "█ ███ █ █▄ ▄▀▄█ ▄▄█▄▀███▄█▄▀█▄██ █ ███ █\n" + + "█ ▀▀▀ █ ▄▄▄█ ▀▀ ▄ ▀█▀▄▀▀ ▄▀▀▀ █ █ ▀▀▀ █\n" + + "▀▀▀▀▀▀▀ ▀ ▀ ▀ ▀▄█▄▀ ▀▄▀ ▀ ▀ ▀▄█▄█ ▀▀▀▀▀▀▀\n" + + "▀▀▄ ▀▀▀▄ ▀ ▀█▄▄▄▄▀▀█ █ ▀█▄▀█▀▄█▀ █▄▀█▀▀\n" + + "▀▄ ▀█ ▀▄▄▀ █ ▄▀██▄▄▀▄ ▀█▀ █▀█▀▄ █▀███ █\n" + + "███▄▀▀▀▄█ █▀▀█▀▀▄▄▀█▀ █▄▀█ ▄█ ▄ ▀▄▄▄█ ▄▄\n" + + " ▀ ▄▀▄█▄▄ ▄ ▄ ▀█▄█▄█▀██▀ █ █ ▄▀▄█ █▀▄█\n" + + " ▀▄ ▄█▀█▀ ▀▀ ▀█▄█ ▀▄██▄▄▀█▀ ▄█▀▀▄████ \n" + + "▄ ▄▀█▄▀ █████▀▀▄ ▀ ██▀██ ▀▀ ▄▄▄▀▄▀▄▀ ▀▄▀\n" + + " █▄▄ ▀▀█▄█ ▄█▄ ▀ ▄▄▄▀█▄▀▄▄ ▄ ▄ ▀▄▄▄▄ ██\n" + + "▀▀█▀▀▄▀ ▄█▀▀ █▄█▄ ▀▄ ██ ▄█ █▀▄ ▀██▀▄▀▄█\n" + + "▀▄█ ▄▀ ▄ █▄▄▄▀▄▄ █▀▄▀ ▀▀ ███▀▀▀▀▄█▄▀█ ▀\n" + + "▀ ▄█ ▀▀ ▄▀ █▄ ▄▀ █▄ ▀▄█▀▀ ▀██▀ █ ▀▄▀▄▀▄▀\n" + + "█▀█▄▀▀▀ ██▄ ▀▀▀ ▄▄▀█▀█▀▄ ▄▄▄▄ ▀ █▄▄▄▀███\n" + + " ▀██▀▀▀█▀ ▄ ▀█ █▄▄██ ▀ ▀ █▄ ▀▄▀ █▀ ▀\n" + + "▀▀ ▀▀▄▀▀█ ▀ ███ ▀▄▀▀▀▄▄▄█ █▀▀▀███▀▀\n" + + "█▀▀▀▀▀█ ▄█▀▀ █▀█▄ ▀▄▄▄▄█▀ ▄▀ ▄ ██ ▀ █▄▀ ▀\n" + + "█ ███ █ ▀▀█▀▄█▄ ▀▄▄██▀▄█▄ ▀█▀▄▀██▀▀▀▀▄▀▄\n" + + "█ ▀▀▀ █ ▄█ █▀▀▄█▄█▄ ▄ █▀▀▄▀▄█ █▄▄▀▄▄█▀▄█\n" + + "▀▀▀▀▀▀▀ ▀ ▀ ▀▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀▀ " + EOL; + + doMain(); + + assertEquals(ExpectedQRCode, getOutput()); + } + + private void doMain() { + + setInput(); + + ServiceLocator.DEFAULT_CHARSET = StandardCharsets.UTF_8; + + RudefoxBurrow.main(getArgs()); + } } \ No newline at end of file