Compare commits

...

25 Commits

Author SHA1 Message Date
B.J. Dweck fea979d79d Creating new release 2021-03-14 10:22:43 +02:00
B.J. Dweck 6c781accb0 Made the interactive D8 entropy reader more forgiving for a better UX 2021-03-14 10:19:25 +02:00
B.J. Dweck 4fd4bb407b Made the interactive binary entropy reader more forgiving for a better UX 2021-03-14 10:14:19 +02:00
B.J. Dweck dc6179dcd0 Refactored InteractiveBinaryEntropyGenerator code
BUGFIX: Implemented non-interactive entropy for D8 and Binary
2021-03-12 14:32:04 +02:00
B.J. Dweck ce7bd27de0 Implemented support for custom binary entropy source 2021-03-12 12:36:02 +02:00
B.J. Dweck f167b704bf Fixed breaking test (erroneously detecting ANSI environment) and mis-colorized space at the end of the bit string, at the CHECKSUM 2020-09-25 13:12:20 +02:00
B.J. Dweck c3d2c68860 Still couldn't get yellow to show up properly on the RPi... gave up and changed the color palette 2020-09-25 12:29:54 +02:00
B.J. Dweck 785cd3f713 Yellow still was being a jerk 2020-09-25 11:36:08 +02:00
B.J. Dweck 8dffe41150 Changed colors; wasn't working on the RPi 2020-09-25 11:26:00 +02:00
B.J. Dweck 21660b6c58 Leveraged com.diogonunes:JColor library to colorize output bits on supporting ANSI platforms 2020-09-25 11:09:04 +02:00
B.J. Dweck 65b5c0713c Upgraded to vixen 0.0.3 2020-09-06 15:57:59 +02:00
B.J. Dweck baa2387f71 Added MIT license 2020-09-06 12:18:45 +02:00
B.J. Dweck 525621bb00 Changed README.md verbiage 2020-09-03 00:58:38 +02:00
B.J. Dweck a80561a6c3 Fixed the build.gradle 2020-09-03 00:23:09 +02:00
B.J. Dweck c8d2a4ad75 Updated README.md 2020-09-02 23:58:01 +02:00
B.J. Dweck 7af3fa0123 Merge remote-tracking branch 'origin/master' 2020-09-02 22:44:21 +02:00
B.J. Dweck fd7484b140 Updated maven repo info 2020-09-02 22:43:55 +02:00
B.J. Dweck eb0a92fb4d Made gradlew executable 2020-09-02 22:35:58 +02:00
B.J. Dweck d752a56f2b Added Picocli Autocomplete script generation 2020-09-02 12:12:23 +02:00
B.J. Dweck 8e5fe74f5c Updated build to detect that 'dirty' and 'non-master' branches are not releases 2020-09-02 12:11:37 +02:00
B.J. Dweck 78c03988ea Renamed project from 'cold' to 'burrow' 2020-09-01 16:13:15 +02:00
B.J. Dweck ac99468a9d Added distro publication 2020-09-01 16:04:28 +02:00
B.J. Dweck 47d27920d4 Updated README.md 2020-09-01 15:13:05 +02:00
B.J. Dweck 9ee5ebbf08 Added gradle wrapper 2020-09-01 15:09:50 +02:00
B.J. Dweck 7e6c04a44e Fixed repo URL 2020-09-01 15:09:16 +02:00
23 changed files with 1508 additions and 938 deletions

2
.gitignore vendored
View File

@ -129,4 +129,6 @@ gradle-app.setting
### Gradle Patch ###
**/build/
gradle.properties
# End of https://www.gitignore.io/api/java,gradle,intellij

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 B.J. Dweck
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,77 +1,31 @@
# Rudefox Cold
# Rudefox Burrow
Rudefox is a pair of command line tools for the creation and management of Bitcoin wallets.
*Offline Bitcoin Seed Generation and Wallet Tool*
+ Use `rudefox-cold` on an air-gapped single-board computer, like a Raspberry Pi, to generate new wallets, obtain addresses and extended public keys from existing wallets and sign transactions offline.
+ Use `rudefox-hot` on a connected device to view your balance and utxo's and to create new, unsigned transactions.
Use Burrow on an air-gapped, single-board computer, like a Raspberry Pi, to generate new seed words, derive addresses and extended public keys from existing seed words or sign PSBT transactions offline. Check out the self-audited seed generation feature: [ShowMyWork](https://rudefox.io/blog/2020-07-16-show-my-work.html).
## Mnemonic Generation
[Rudefox Burrow](https://rudefox.io/burrow/) is released under the [MIT License](LICENSE.md) by B.J. Dweck
## Getting Started
If you simply want to dive in and try your hand at generating a self-audited seed, see the [Burrow Quick Start](https://www.rudefox.io/burrow/quick-start.html) guide.
## Build
### Linux
* Install the Java 8 SDK (Java 8+ is not already installed)
```bash
alice@cold.machine:~$ rudefox-cold mnemonic --interactive --dice-entropy --bits 128
Input 50 dice rolls [1-6]: 234322343242422344161254151
Input 23 more dice rolls [1-6]: 33116265515343114314456
Mnemonic Sentence: mountain tilt wing silk rude fox almost volume wine media verify card
sudo apt-get install -y openjdk-8-jdk-headless
```
### Mnemonic Options
* Clone the repository and build
```bash
usage: rudefox-cold mnemonic
-b,--bits <128|160|192|224|256> bits of entropy (default: 256)
-d,--dice-entropy use dice entropy source
-e,--events <EVENT_STRING> string representing events from entropy source
-h,--help display help message
-i,--interactive use interactive command line mode
git clone https://git.rudefox.io/rudefox/burrow.git
cd burrow
./gradlew build distTar
```
## Xpub Export
```bash
alice@cold.machine:~$ rudefox-cold wallet --sentence "stove prefer lunch collect small orphan wasp size beyond auction
guilt great" --passphrase apple
xpub6CKy5SECeJipZid8dF3bopoMGdRzd7hMJuPzMGesZCobrMSssZyASexzXuzRTPVLcqqdyAEZJKPMGvDthgZW2Z3mPHLohxEAVbkvGKAXjqx
```
### QR Code Generation
```bash-qrcode
alice@cold.machine:~$ rudefox-cold wallet --sentence "stove prefer lunch collect small orphan wasp size beyond auction
guilt great" --passphrase apple -q
█▀▀▀▀▀█ ▄█ █▄█ ▀▄▀ ▀▄▄▄██▄ █▀▄ █▀▀▀▀▀█
█ ███ █ ▀ ▀▄█▄▄▄▄██▄▄▄█ █ █▄ █ ███ █
█ ▀▀▀ █ █▄▀ █▄█▄▄▄▀█▀▄ ▄▀▀█▀ ▄▄▄ █ ▀▀▀ █
▀▀▀▀▀▀▀ █▄▀▄█ ▀▄█▄▀ ▀▄█ █ █▄▀▄▀ ▀ ▀▀▀▀▀▀▀
██ █ ▀█ ███ ██ ▄█▄▀▄▀▀██ █▀▄▄▀▀▀ ▀▀▄
██ ▀ █▀█▄▀█▄██▀ ▀ █▀ ▄▄▄▀██ █▀▀▀█ █▀ █▀
█▀█▄ ▀█▀▀ ▄█▄█▄▀ █▀█ ██▀▄█▀ ▄██▀█ ▀ ▀
▀██ ▀▄▄▀▄ ██ ▀██▄▀▄█▀▀█▄▀▄▀█▀▄▄▀▀█▄▀ █▀
▀█▄▄ ▀▀▄ ▀▀▀▄▀ ██▄▄ ▀ ▀ █▀ ▀▄▀ █ █ █▀█▄ ▄
███ █ ▀██ ▀▄▀█ ▄▄▀ ▄ ▄█▄▄ ▀▄██▄ ▀▄█▀▀ ▀▀
▀█▄█▄ ▀ ▀▄▄█ ▀▄ █ ▄▀▀ ▄▄▀▄█▄█ ██▀▀▀▀▀ █
▀ ▀▀▄ ▀▄█ ▄ ██▄▀▀██▄▄█▄ ▄█▄ █▀ ▄ ▄▀██
█ ▀▄▄▄▀█▀██▄▄▀▄▄▀█▄ █ ▄██▀▄ ▄ ▄▄█▀█ █ ▄
▀ ██ ▀▀▀▄ ▀██▄▄▄ ██ █▀█▀▄█ ▄▄▀▄▀▀ ▄█ ▀█▀
▀▄▄█ █▀ █▄▀ ██▀█▄ █▄▀████▀▄██ ▄▄▀█ █▀█▀▀█
▀ ▄▄ ▀▄▄▄▄▀█ ▄▀▀█████ ▀▀▀▄ █ ▄█▀█▀▀█▀▀▀
▀ ▀▀ ▀▀▄▀▀▄▄▀█▀▄ ▄███ ▀██▀▀█ █▄█▀▀▀█▄ █
█▀▀▀▀▀█ ▀▄█▄▀ ▄ █ ▀▄▄▄▄▄█▀ ▀▀ █ ▀ █ ▄█▄
█ ███ █ ▄ █▄ ▄ █▄▄ ▀█ █ ▄ ▀██ █▀█▀█ ▀█▀
█ ▀▀▀ █ ▄▄▀█ █ ▄ ▄█▄ ▄▄▀█▄▀ ▄█ █ ▀ █▀ █▀
▀▀▀▀▀▀▀ ▀▀ ▀ ▀ ▀▀ ▀ ▀ ▀▀▀ ▀▀▀
alice@cold.machine:~$
```
### Wallet Options
```bash
usage: rudefox-cold wallet
-h,--help display help message
-p,--passphrase <PASSPHRASE> optional seed passphrase
-s,--sentence <SENTENCE> mnemonic sentence
-q,--qrcode optional seed passphrase
```
# Pending Use Cases
- verify seed in interactive mode
* Find the distribution `.tar` file in `build/distributions/`

View File

@ -8,7 +8,7 @@ plugins {
repositories {
maven {
url "https://repo.rudefox.io/maven-public"
url "https://repo.rudefox.io/repository/maven-releases/"
}
mavenCentral()
@ -17,20 +17,28 @@ repositories {
sourceCompatibility = 1.8
targetCompatibility = 1.8
def isRelease = versionDetails().commitDistance == 0
group 'io.rudefox'
version isRelease ? gitVersion.call() : gitVersion.call() + "-SNAPSHOT"
application {
applicationName 'rudefox-cold'
mainClassName = 'io.rudefox.cold.RudefoxCold'
mainClassName = 'io.rudefox.burrow.RudefoxBurrow'
}
def isRelease = versionDetails().commitDistance == 0 &&
versionDetails().isCleanTag &&
((versionDetails().branchName == 'master') || (versionDetails().branchName == 'null'))
version isRelease ? gitVersion.call() : gitVersion.call() + "-${versionDetails().branchName}-SNAPSHOT"
println "${group}:${project.name}:${version}" + ' ' + (isRelease ? '(RELEASE)' : '(SNAPSHOT)')
run {
standardInput = System.in
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
def junitVersion = "5.6.2"
tasks.withType(Test) {
@ -38,9 +46,10 @@ tasks.withType(Test) {
}
dependencies {
compile 'io.rudefox:vixen:0.0.2'
compile 'info.picocli:picocli:4.0.4'
compile 'io.rudefox:vixen:0.0.3'
compile 'info.picocli:picocli:4.5.1'
compile 'com.google.zxing:core:3.4.0'
compile 'com.diogonunes:JColor:5.0.0'
testCompile 'com.bjdweck.test:commons-test:0.0.1'
testCompile "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
@ -50,12 +59,14 @@ dependencies {
publishing {
publications {
jar(MavenPublication) {
distro(MavenPublication) {
from components.java
artifact distZip
artifact distTar
pom {
name = 'Rudefox Burrow'
description = 'Bitcoin offline seed generation and tools'
url = 'https://rudefox.io'
url = 'https://rudefox.io/burrow'
developers {
developer {
name = 'B.J. Dweck'

View File

@ -1,15 +1,2 @@
# bitcoind connection info for integration testing
bitcoind_host=backend.sample.com
bitcoind_port=8332
bitcoind_username=admin
bitcoind_password=l6RYSFfGHosjvHDfK_f7GSBkdGHk26fjHchZrJ87dSH=
# public esplora URL for integration testing
esplora_url=http://backend.sample.com:8080
esplora_testnet_url=http://backend.sample.com:8081/testnet/
# web server credentials for website deployment (use in the ':deployWebsite' task)
webserver_host=web.sample.com
webserver_user=http_admin
webserver_password=mycrazypassword
webserver_target_dir=/path/to/webroot/without-ending-slash
rudefox_maven_username=john
rudefox_maven_password=mypassword

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

183
gradlew vendored Executable file
View File

@ -0,0 +1,183 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

100
gradlew.bat vendored Normal file
View File

@ -0,0 +1,100 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -1,47 +1,59 @@
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 EventBuffer {
private final int eventBase;
private final StringBuilder buffer;
private final int targetBitsOfEntropy;
EventBuffer(int targetBitsOfEntropy, int eventBase) {
this.eventBase = eventBase;
this.targetBitsOfEntropy = targetBitsOfEntropy;
this.buffer = new StringBuilder(getRequiredEvents());
}
int getRequiredEvents() {
return (int) Math.ceil(this.targetBitsOfEntropy * Math.log(2) / Math.log(eventBase));
}
void appendBinaryEvents(String eventString) {
for (char inChar : eventString.toCharArray())
if (inChar >= '0' && inChar <= '1' && events() < getRequiredEvents())
append(inChar);
}
void appendD6Events(String eventString) {
for (char inChar : eventString.toCharArray())
if (inChar >= '1' && inChar <= '6' && events() < getRequiredEvents())
append((char) (inChar - 1));
}
void appendD8Events(String eventString) {
for (char inChar : eventString.toCharArray())
if (inChar >= '1' && inChar <= '8' && events() < getRequiredEvents())
append((char) (inChar - 1));
}
Entropy toEntropy() {
UnsignedInt entropy = new UnsignedInt(toString(), eventBase);
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();
}
}

View File

@ -0,0 +1,74 @@
package io.rudefox.burrow;
import java.util.Scanner;
import java.util.regex.Pattern;
public class InteractiveBinaryEntropyGenerator extends InteractiveEntropyGenerator {
public InteractiveBinaryEntropyGenerator(int targetBitsOfEntropy, Scanner inputScanner, boolean ansiColorOutput) {
super(inputScanner, ansiColorOutput, targetBitsOfEntropy, 1);
}
@Override
protected String readNextEventSetString() {
StringBuilder eventSetBuffer = new StringBuilder();
int remainingEvents = eventsPerEventSet;
Pattern eventSetPattern = Pattern.compile("[01]+");
while (remainingEvents > 0) {
System.out.printf("Input %d coin tosses [0-1]: ", remainingEvents);
String nextInput = inputScanner.next();
if (!eventSetPattern.matcher(nextInput).matches()) {
System.out.printf(
"Invalid input! Enter %d-digit sequence of 1's and 0's (for example, 1011101010)" + System.lineSeparator(),
remainingEvents);
continue;
}
eventSetBuffer.append(nextInput);
remainingEvents -= nextInput.length();
}
if (remainingEvents < 0)
System.out.printf("NOTE: Discarding the %d extra coin toss(es) entered" + System.lineSeparator(), -remainingEvents);
return eventSetBuffer.substring(0, eventsPerEventSet);
}
@Override
protected String getEventValueFormatString(int eventIndexWithinSet) {
return "";
}
@Override
protected int toZeroBasedInteger(int eventDecimalValue) {
return eventDecimalValue;
}
@Override
protected void outputEventValuesLine(StringBuilder eventValuesLine) { }
@Override
protected StringBuilder getInitialBitsLine() {
return new StringBuilder("| ");
}
@Override
protected void padBitsLine(StringBuilder eventSetBitsLine, int bitIndexWithinEventSet, int bitIndexWithinEntropy) {
if (bitIndexWithinEventSet == BITS_PER_EVENT_SET - 1)
eventSetBitsLine.append(" |");
else if (bitIndexWithinEventSet % BITS_PER_WORD == 0 || bitIndexWithinEventSet % BITS_PER_WORD == 6)
eventSetBitsLine.append(styleBit(" ", bitIndexWithinEntropy, bitIndexWithinEventSet));
else if (bitIndexWithinEventSet % BITS_PER_WORD == BITS_PER_WORD - 1)
eventSetBitsLine.append(" | ");
}
@Override
protected String getSeedWordLineFormatString() {
return "|%-15s|%-15s|%-15s|";
}
}

View File

@ -0,0 +1,76 @@
package io.rudefox.burrow;
import java.util.Scanner;
import java.util.regex.Pattern;
public class InteractiveDice8EntropyGenerator extends InteractiveEntropyGenerator {
public InteractiveDice8EntropyGenerator(int targetBitsOfEntropy, Scanner inputScanner, boolean ansiColorOutput) {
super(inputScanner, ansiColorOutput, targetBitsOfEntropy, 3);
}
@Override
protected String readNextEventSetString() {
StringBuilder eventSetBuffer = new StringBuilder();
int remainingEvents = eventsPerEventSet;
Pattern eventSetPattern = Pattern.compile("[1-8]+");
while (remainingEvents > 0) {
System.out.printf("Input %d x 8-sided dice rolls [1-8]: ", remainingEvents);
String nextInput = inputScanner.next();
if (!eventSetPattern.matcher(nextInput).matches()) {
System.out.printf(
"Invalid input! Enter %d-digit sequence of digits from 1 to 8 (for example, 8765375812)" + System.lineSeparator(),
remainingEvents);
continue;
}
eventSetBuffer.append(nextInput);
remainingEvents -= nextInput.length();
}
if (remainingEvents < 0)
System.out.printf("NOTE: Discarding the %d extra dice roll(s) entered" + System.lineSeparator(), -remainingEvents);
return eventSetBuffer.substring(0, eventsPerEventSet);
}
@Override
protected String getEventValueFormatString(int eventIndexWithinSet) {
return eventIndexWithinSet == bitsPerEvent || eventIndexWithinSet == 7 ? " %d |" : " %d |";
}
@Override
protected int toZeroBasedInteger(int eventDecimalValue) {
return eventDecimalValue - 1;
}
@Override
protected void outputEventValuesLine(StringBuilder eventValuesLine) {
System.out.println(eventValuesLine);
}
@Override
protected StringBuilder getInitialBitsLine() {
return new StringBuilder("|");
}
@Override
protected void padBitsLine(StringBuilder eventSetBitsLine, int bitIndexWithinEventSet, int bitIndexWithinEntropy) {
if (bitIndexWithinEventSet == BITS_PER_EVENT_SET - 1)
eventSetBitsLine.append("|");
else if (bitIndexWithinEventSet % bitsPerEvent == bitsPerEvent - 1)
eventSetBitsLine.append(styleBit(" ", bitIndexWithinEntropy, bitIndexWithinEventSet));
else if (bitIndexWithinEventSet % BITS_PER_WORD == BITS_PER_WORD - 1)
eventSetBitsLine.append(" | ");
}
@Override
protected String getSeedWordLineFormatString() {
return "|%-15s|%-17s|%-15s|";
}
}

View File

@ -0,0 +1,154 @@
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();
}

View File

@ -1,125 +1,142 @@
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 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);
}
}

View File

@ -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<EncodeHintType, Object> 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<EncodeHintType, Object> 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");
}
}

View File

@ -1,32 +1,33 @@
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.AutoComplete;
import picocli.CommandLine;
@CommandLine.Command(
name = "burrow",
synopsisSubcommandLabel = "COMMAND",
description = "Offline wallet tool",
subcommands = {MnemonicCommand.class, WalletCommand.class, AutoComplete.GenerateCompletion.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");
}
}

View File

@ -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();
}

View File

@ -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 = "<n>", 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 = "<n>", defaultValue = "0")
int addressSeq,
@CommandLine.Option(names = {"-n", "--count"}, description = "number of addresses to list",
paramLabel = "<n>", 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 = "<n>", 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 = "<n>", defaultValue = "0")
int addressSeq,
@CommandLine.Option(names = {"-n", "--count"}, description = "number of addresses to list",
paramLabel = "<n>", 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");
}
}

View File

@ -1,115 +0,0 @@
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);
}
}

View File

@ -0,0 +1,62 @@
package io.rudefox.burrow;
import com.bjdweck.test.CliTestFixture;
import org.junit.jupiter.api.Test;
import java.io.UnsupportedEncodingException;
class coin_entropy_tests extends CliTestFixture {
@Test
void with_arguments_interactive_coin_should_generate_mnemonic_sentence() throws UnsupportedEncodingException {
System.setProperty("picocli.ansi", "false");
withArgs("mnemonic -i2 --bits 128");
expectedOutput("Input 33 coin tosses [0-1]: ");
provideInput("000001010011100101110111000001010" + EOL);
expectedOutput(EOL);
expectedOutput("| 0 000010 1001 | 1 100101 1101 | 1 100000 1010 |" + EOL);
expectedOutput("| 1. ahead | 2. slight | 3. scout |" + EOL);
expectedOutput(EOL);
expectedOutput("Input 33 coin tosses [0-1]: ");
provideInput("000001010011100101110111000001010" + EOL);
expectedOutput(EOL);
expectedOutput("| 0 000010 1001 | 1 100101 1101 | 1 100000 1010 |" + EOL);
expectedOutput("| 4. ahead | 5. slight | 6. scout |" + EOL);
expectedOutput(EOL);
expectedOutput("Input 33 coin tosses [0-1]: ");
provideInput("000001010011100101110111000001010" + EOL);
expectedOutput(EOL);
expectedOutput("| 0 000010 1001 | 1 100101 1101 | 1 100000 1010 |" + EOL);
expectedOutput("| 7. ahead | 8. slight | 9. scout |" + EOL);
expectedOutput(EOL);
expectedOutput("Input 33 coin tosses [0-1]: ");
provideInput("000001010011100101110111000001010" + EOL);
expectedOutput(EOL);
expectedOutput("| 0 000010 1001 | 1 100101 1101 | 1 100000 ---- |" + 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());
}
}

View File

@ -1,64 +1,66 @@
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 {
System.setProperty("picocli.ansi", "false");
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());
}
}

View File

@ -1,71 +1,95 @@
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_non_interactive_binary_entropy_should_generate_mnemonic_sentence() throws UnsupportedEncodingException {
withArgs("mnemonic --binary-entropy --events 10100101010010101010101000101010101001010100101010101010001010101010010101001010101010100010101010100101010010101010101000101010 --bits 128");
expectedOutput("pipe fetch melt enhance primary best news fetch click clean pride feed" + 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();
}
@Test
void with_arguments_without_interactive_d8_dice_should_generate_mnemonic_sentence() throws UnsupportedEncodingException {
withArgs("mnemonic --dice8-entropy --events 23438688738737812541514768125415147632873218 --bits 128");
expectedOutput("fashion wave tourist notice alert marine vote alert marine vocal dish aware" + EOL);
doMain();
verifyOutput();
}
private void doMain() {
setInput();
RudefoxBurrow.main(getArgs());
}
}

View File

@ -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());
}
}