diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..168fe7b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +/ref/java/out +/ref/java/build +/ref/java/.gradle diff --git a/ref/java/README.md b/ref/java/README.md new file mode 100644 index 0000000..f192042 --- /dev/null +++ b/ref/java/README.md @@ -0,0 +1,50 @@ +# Bech32 Java + +## Usage + +This [Bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) implementation is +designed to be a standalone drop-in library for your Java and Android projects.
+ +Based entirely on the Java Standard library, it allows either to validate a **Bech32 String** or a **Bech32 Segwit Address**.
+In the latter case it's possible to decode, with the **SegwitAddress** class, an address in a way that throws an exception with +the releated issue if it's an invalid one. Otherwise it's possible to get back a *null* object if the +address is invalid. + + +```java +Bech32 bech32 = new Bech32(); + +// Returns a valid Bech32Decoded object +mBech32.decode("A12UEL5L"); + +// Throws a Bech32ValidationException +mBech32.decode("x1b4n0q5v"); +``` +```java +SegwitAddress segwitAddress = new SegwitAddress(); + +// Returns a valid SegwitAddress object +segwitAddress.decode("bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "bc"); + +// Returns a null object +segwitAddress.decode("bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", "bc"); + +// Throws a Bech32ValidationException +segwitAddress.decodeThrowing("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", "bc"); +``` + +## Running tests + +The project is integrated with Gradle, but thanks to its design can be built using your favourite build system.
+It depends on JUnit's `org.junit.Assert` class in order to assert the test results.
+In order to run the tests launch via the command line these two gradle tasks.
+Note that a **gradlew** binary is already included when cloning this repository. + +``` +$ ./gradlew --refresh-dependencies +$ ./gradlew test + +BUILD SUCCESSFUL in 0s +3 actionable tasks: 3 up-to-date +``` + diff --git a/ref/java/build.gradle b/ref/java/build.gradle new file mode 100644 index 0000000..3cf7493 --- /dev/null +++ b/ref/java/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'java' +} + +group 'com.conio.wallet' +version '1.0' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' +} \ No newline at end of file diff --git a/ref/java/gradle/wrapper/gradle-wrapper.jar b/ref/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..28861d2 Binary files /dev/null and b/ref/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/ref/java/gradle/wrapper/gradle-wrapper.properties b/ref/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..115e6ac --- /dev/null +++ b/ref/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/ref/java/gradlew b/ref/java/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/ref/java/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## 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="" + +# 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, switch paths to Windows format before running java +if $cygwin ; 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=$((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" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/ref/java/src/main/java/com/conio/wallet/Bech32.java b/ref/java/src/main/java/com/conio/wallet/Bech32.java new file mode 100644 index 0000000..e18754c --- /dev/null +++ b/ref/java/src/main/java/com/conio/wallet/Bech32.java @@ -0,0 +1,384 @@ +/* + * # Copyright (c) 2019 Lorenzo Zanotto + * # + * # 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. + */ + +package com.conio.wallet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +public class Bech32 { + + public static class Bech32Decoded { + private String hrp; + private byte[] data; + + Bech32Decoded(String hrp, byte[] data) { + this.hrp = hrp; + this.data = data; + } + + public String getHrp() { + return hrp; + } + + public byte[] getData() { + return data; + } + } + + private final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + + private final int[] GENERATOR = new int[]{ + 0x3b6a57b2, 0x26508e6d, + 0x1ea119fa, 0x3d4233dd, + 0x2a1462b3 + }; + + private final int[] GF1024_EXP = new int[]{ + 1, 303, 635, 446, 997, 640, 121, 142, 959, 420, 350, 438, 166, 39, 543, + 335, 831, 691, 117, 632, 719, 97, 107, 374, 558, 797, 54, 150, 858, 877, + 724, 1013, 294, 23, 354, 61, 164, 633, 992, 538, 469, 659, 174, 868, 184, + 809, 766, 563, 866, 851, 257, 520, 45, 770, 535, 524, 408, 213, 436, 760, + 472, 330, 933, 799, 616, 361, 15, 391, 756, 814, 58, 608, 554, 680, 993, + 821, 942, 813, 843, 484, 193, 935, 321, 919, 572, 741, 423, 559, 562, + 589, 296, 191, 493, 685, 891, 665, 435, 60, 395, 2, 606, 511, 853, 746, + 32, 219, 284, 631, 840, 661, 837, 332, 78, 311, 670, 887, 111, 195, 505, + 190, 194, 214, 709, 380, 819, 69, 261, 957, 1018, 161, 739, 588, 7, 708, + 83, 328, 507, 736, 317, 899, 47, 348, 1000, 345, 882, 245, 367, 996, 943, + 514, 304, 90, 804, 295, 312, 793, 387, 833, 249, 921, 660, 618, 823, 496, + 722, 30, 782, 225, 892, 93, 480, 372, 112, 738, 867, 636, 890, 950, 968, + 386, 622, 642, 551, 369, 234, 846, 382, 365, 442, 592, 343, 986, 122, + 1023, 59, 847, 81, 790, 4, 437, 983, 931, 244, 64, 415, 529, 487, 944, + 35, 938, 664, 156, 583, 53, 999, 222, 390, 987, 341, 388, 389, 170, 721, + 879, 138, 522, 627, 765, 322, 230, 440, 14, 168, 143, 656, 991, 224, 595, + 550, 94, 657, 752, 667, 1005, 451, 734, 744, 638, 292, 585, 157, 872, + 590, 601, 827, 774, 930, 475, 571, 33, 500, 871, 969, 173, 21, 828, 450, + 1009, 147, 960, 705, 201, 228, 998, 497, 1021, 613, 688, 772, 508, 36, + 366, 715, 468, 956, 725, 730, 861, 425, 647, 701, 221, 759, 95, 958, 139, + 805, 8, 835, 679, 614, 449, 128, 791, 299, 974, 617, 70, 628, 57, 273, + 430, 67, 750, 405, 780, 703, 643, 776, 778, 340, 171, 1022, 276, 308, + 495, 243, 644, 460, 857, 28, 336, 286, 41, 695, 448, 431, 364, 149, 43, + 233, 63, 762, 902, 181, 240, 501, 584, 434, 275, 1008, 444, 443, 895, + 812, 612, 927, 383, 66, 961, 1006, 690, 346, 3, 881, 900, 747, 271, 672, + 162, 402, 456, 748, 971, 755, 490, 105, 808, 977, 72, 732, 182, 897, 625, + 163, 189, 947, 850, 46, 115, 403, 231, 151, 629, 278, 874, 16, 934, 110, + 492, 898, 256, 807, 598, 700, 498, 140, 481, 91, 523, 860, 134, 252, 771, + 824, 119, 38, 816, 820, 641, 342, 757, 513, 577, 990, 463, 40, 920, 955, + 17, 649, 533, 82, 103, 896, 862, 728, 259, 86, 466, 87, 253, 556, 323, + 457, 963, 432, 845, 527, 745, 849, 863, 1015, 888, 488, 567, 727, 132, + 674, 764, 109, 669, 6, 1003, 552, 246, 542, 96, 324, 781, 912, 248, 694, + 239, 980, 210, 880, 683, 144, 177, 325, 546, 491, 326, 339, 623, 941, 92, + 207, 783, 462, 263, 483, 517, 1012, 9, 620, 220, 984, 548, 512, 878, 421, + 113, 973, 280, 962, 159, 310, 945, 268, 465, 806, 889, 199, 76, 873, 865, + 34, 645, 227, 290, 418, 693, 926, 80, 569, 639, 11, 50, 291, 141, 206, + 544, 949, 185, 518, 133, 909, 135, 467, 376, 646, 914, 678, 841, 954, + 318, 242, 939, 951, 743, 1017, 976, 359, 167, 264, 100, 241, 218, 51, 12, + 758, 368, 453, 309, 192, 648, 826, 553, 473, 101, 478, 673, 397, 1001, + 118, 265, 331, 650, 356, 982, 652, 655, 510, 634, 145, 414, 830, 924, + 526, 966, 298, 737, 18, 504, 401, 697, 360, 288, 1020, 842, 203, 698, + 537, 676, 279, 581, 619, 536, 907, 876, 1019, 398, 152, 1010, 994, 68, + 42, 454, 580, 836, 99, 565, 137, 379, 503, 22, 77, 582, 282, 412, 352, + 611, 347, 300, 266, 570, 270, 911, 729, 44, 557, 108, 946, 637, 597, 461, + 630, 615, 238, 763, 681, 718, 334, 528, 200, 459, 413, 79, 24, 229, 713, + 906, 579, 384, 48, 893, 370, 923, 202, 917, 98, 794, 754, 197, 530, 662, + 52, 712, 677, 56, 62, 981, 509, 267, 789, 885, 561, 316, 684, 596, 226, + 13, 985, 779, 123, 720, 576, 753, 948, 406, 125, 315, 104, 519, 426, 502, + 313, 566, 1016, 767, 796, 281, 749, 740, 136, 84, 908, 424, 936, 198, + 355, 274, 735, 967, 5, 154, 428, 541, 785, 704, 486, 671, 600, 532, 381, + 540, 574, 187, 88, 378, 216, 621, 499, 419, 922, 485, 494, 476, 255, 114, + 188, 668, 297, 400, 918, 787, 158, 25, 458, 178, 564, 422, 768, 73, 1011, + 717, 575, 404, 547, 196, 829, 237, 394, 301, 37, 65, 176, 106, 89, 85, + 675, 979, 534, 803, 995, 363, 593, 120, 417, 452, 26, 699, 822, 223, 169, + 416, 235, 609, 773, 211, 607, 208, 302, 852, 965, 603, 357, 761, 247, + 817, 539, 250, 232, 272, 129, 568, 848, 624, 396, 710, 525, 183, 686, 10, + 285, 856, 307, 811, 160, 972, 55, 441, 289, 723, 305, 373, 351, 153, 733, + 409, 506, 975, 838, 573, 970, 988, 913, 471, 205, 337, 49, 594, 777, 549, + 815, 277, 27, 916, 333, 353, 844, 800, 146, 751, 186, 375, 769, 358, 392, + 883, 474, 788, 602, 74, 130, 329, 212, 155, 131, 102, 687, 293, 870, 742, + 726, 427, 217, 834, 904, 29, 127, 869, 407, 338, 832, 470, 482, 810, 399, + 439, 393, 604, 929, 682, 447, 714, 251, 455, 875, 319, 477, 464, 521, + 258, 377, 937, 489, 792, 172, 314, 327, 124, 20, 531, 953, 591, 886, 320, + 696, 71, 859, 578, 175, 587, 707, 663, 283, 179, 795, 989, 702, 940, 371, + 692, 689, 555, 903, 410, 651, 75, 429, 818, 362, 894, 515, 31, 545, 666, + 706, 952, 864, 269, 254, 349, 711, 802, 716, 784, 1007, 925, 801, 445, + 148, 260, 658, 385, 287, 262, 204, 126, 586, 1004, 236, 165, 854, 411, + 932, 560, 19, 215, 1002, 775, 653, 928, 901, 964, 884, 798, 839, 786, + 433, 610, 116, 855, 180, 479, 910, 1014, 599, 915, 905, 306, 516, 731, + 626, 978, 825, 344, 605, 654, 209 + }; + + private final int[] GF1024_LOG = new int[]{ + -1, 0, 99, 363, 198, 726, 462, 132, 297, 495, 825, 528, 561, 693, 231, + 66, 396, 429, 594, 990, 924, 264, 627, 33, 660, 759, 792, 858, 330, 891, + 165, 957, 104, 259, 518, 208, 280, 776, 416, 13, 426, 333, 618, 339, 641, + 52, 388, 140, 666, 852, 529, 560, 678, 213, 26, 832, 681, 309, 70, 194, + 97, 35, 682, 341, 203, 777, 358, 312, 617, 125, 307, 931, 379, 765, 875, + 951, 515, 628, 112, 659, 525, 196, 432, 134, 717, 781, 438, 440, 740, + 780, 151, 408, 487, 169, 239, 293, 467, 21, 672, 622, 557, 571, 881, 433, + 704, 376, 779, 22, 643, 460, 398, 116, 172, 503, 751, 389, 1004, 18, 576, + 415, 789, 6, 192, 696, 923, 702, 981, 892, 302, 816, 876, 880, 457, 537, + 411, 539, 716, 624, 224, 295, 406, 531, 7, 233, 478, 586, 864, 268, 974, + 338, 27, 392, 614, 839, 727, 879, 211, 250, 758, 507, 830, 129, 369, 384, + 36, 985, 12, 555, 232, 796, 221, 321, 920, 263, 42, 934, 778, 479, 761, + 939, 1006, 344, 381, 823, 44, 535, 866, 739, 752, 385, 119, 91, 566, 80, + 120, 117, 771, 675, 721, 514, 656, 271, 670, 602, 980, 850, 532, 488, + 803, 1022, 475, 801, 878, 57, 121, 991, 742, 888, 559, 105, 497, 291, + 215, 795, 236, 167, 692, 520, 272, 661, 229, 391, 814, 340, 184, 798, + 984, 773, 650, 473, 345, 558, 548, 326, 202, 145, 465, 810, 471, 158, + 813, 908, 412, 441, 964, 750, 401, 50, 915, 437, 975, 126, 979, 491, 556, + 577, 636, 685, 510, 963, 638, 367, 815, 310, 723, 349, 323, 857, 394, + 606, 505, 713, 630, 938, 106, 826, 332, 978, 599, 834, 521, 530, 248, + 883, 32, 153, 90, 754, 592, 304, 635, 775, 804, 1, 150, 836, 1013, 828, + 324, 565, 508, 113, 154, 708, 921, 703, 689, 138, 547, 911, 929, 82, 228, + 443, 468, 480, 483, 922, 135, 877, 61, 578, 111, 860, 654, 15, 331, 851, + 895, 484, 320, 218, 420, 190, 1019, 143, 362, 634, 141, 965, 10, 838, + 632, 861, 34, 722, 580, 808, 869, 554, 598, 65, 954, 787, 337, 187, 281, + 146, 563, 183, 668, 944, 171, 837, 23, 867, 541, 916, 741, 625, 123, 736, + 186, 357, 665, 977, 179, 156, 219, 220, 216, 67, 870, 902, 774, 98, 820, + 574, 613, 900, 755, 596, 370, 390, 769, 314, 701, 894, 56, 841, 949, 987, + 631, 658, 587, 204, 797, 790, 522, 745, 9, 502, 763, 86, 719, 288, 706, + 887, 728, 952, 311, 336, 446, 1002, 348, 96, 58, 199, 11, 901, 230, 833, + 188, 352, 351, 973, 3, 906, 335, 301, 266, 244, 791, 564, 619, 909, 371, + 444, 760, 657, 328, 647, 490, 425, 913, 511, 439, 540, 283, 40, 897, 849, + 60, 570, 872, 257, 749, 912, 572, 1007, 170, 407, 898, 492, 79, 747, 732, + 206, 454, 918, 375, 482, 399, 92, 748, 325, 163, 274, 405, 744, 260, 346, + 707, 626, 595, 118, 842, 136, 279, 684, 584, 101, 500, 422, 149, 956, + 1014, 493, 536, 705, 51, 914, 225, 409, 55, 822, 590, 448, 655, 205, 676, + 925, 735, 431, 784, 54, 609, 604, 39, 812, 737, 729, 466, 14, 533, 958, + 481, 770, 499, 855, 238, 182, 464, 569, 72, 947, 442, 642, 24, 87, 989, + 688, 88, 47, 762, 623, 709, 455, 817, 526, 637, 258, 84, 845, 738, 768, + 698, 423, 933, 664, 620, 607, 629, 212, 347, 249, 982, 935, 131, 89, 252, + 927, 189, 788, 853, 237, 691, 646, 403, 1010, 734, 253, 874, 807, 903, + 1020, 100, 802, 71, 799, 1003, 633, 355, 276, 300, 649, 64, 306, 161, + 608, 496, 743, 180, 485, 819, 383, 1016, 226, 308, 393, 648, 107, 19, 37, + 585, 2, 175, 645, 247, 527, 5, 419, 181, 317, 327, 519, 542, 289, 567, + 430, 579, 950, 582, 994, 1021, 583, 234, 240, 976, 41, 160, 109, 677, + 937, 210, 95, 959, 242, 753, 461, 114, 733, 368, 573, 458, 782, 605, 680, + 544, 299, 73, 652, 905, 477, 690, 93, 824, 882, 277, 946, 361, 17, 945, + 523, 472, 334, 930, 597, 603, 793, 404, 290, 942, 316, 731, 270, 960, + 936, 133, 122, 821, 966, 679, 662, 907, 282, 968, 767, 653, 20, 697, 222, + 164, 835, 30, 285, 886, 456, 436, 640, 286, 1015, 380, 840, 245, 724, + 137, 593, 173, 130, 715, 85, 885, 551, 246, 449, 103, 366, 372, 714, 313, + 865, 241, 699, 674, 374, 68, 421, 562, 292, 59, 809, 342, 651, 459, 227, + 46, 711, 764, 868, 53, 413, 278, 800, 255, 993, 318, 854, 319, 695, 315, + 469, 166, 489, 969, 730, 1001, 757, 873, 686, 197, 303, 919, 155, 673, + 940, 712, 25, 999, 63, 863, 972, 967, 785, 152, 296, 512, 402, 377, 45, + 899, 829, 354, 77, 69, 856, 417, 811, 953, 124, 418, 75, 794, 162, 414, + 1018, 568, 254, 265, 772, 588, 16, 896, 157, 889, 298, 621, 110, 844, + 1000, 108, 545, 601, 78, 862, 447, 185, 195, 818, 450, 387, 49, 805, 102, + 986, 1005, 827, 329, 28, 932, 410, 287, 435, 451, 962, 517, 48, 174, 43, + 893, 884, 261, 251, 516, 395, 910, 611, 29, 501, 223, 476, 364, 144, 871, + 998, 687, 928, 115, 453, 513, 176, 94, 168, 667, 955, 353, 434, 382, 400, + 139, 365, 996, 343, 948, 890, 1012, 663, 610, 718, 538, 1008, 639, 470, + 848, 543, 1011, 859, 671, 756, 83, 427, 159, 746, 669, 589, 971, 524, + 356, 995, 904, 256, 201, 988, 62, 397, 81, 720, 917, 209, 549, 943, 486, + 76, 148, 207, 509, 644, 386, 700, 534, 177, 550, 961, 926, 546, 428, 284, + 127, 294, 8, 269, 359, 506, 445, 997, 806, 591, 725, 178, 262, 846, 373, + 831, 504, 305, 843, 553, 378, 1017, 783, 474, 683, 581, 200, 498, 694, + 191, 217, 847, 941, 424, 235, 38, 74, 616, 786, 147, 4, 273, 214, 142, + 575, 992, 463, 983, 243, 360, 970, 350, 267, 615, 766, 494, 31, 1009, + 452, 710, 552, 128, 612, 600, 275, 322, 193 + }; + + private int syndrome(int residue) { + int low = residue & 0x1f; + return low ^ (low << 10) ^ (low << 20) ^ + ((residue >> 5 & 1) == 1 ? 0x31edd3c4 : 0) ^ + ((residue >> 6 & 1 ) == 1 ? 0x335f86a8 : 0) ^ + (((residue >> 7) & 1 ) == 1 ? 0x363b8870 : 0) ^ + (((residue >> 8) & 1 ) == 1 ? 0x3e6390c9 : 0) ^ + (((residue >> 9) & 1 ) == 1 ? 0x2ec72192 : 0) ^ + (((residue >> 10) & 1) == 1 ? 0x1046f79d : 0) ^ + (((residue >> 11) & 1) == 1 ? 0x208d4e33 : 0) ^ + (((residue >> 12) & 1) == 1 ? 0x130ebd6f : 0) ^ + (((residue >> 13) & 1) == 1 ? 0x2499fade : 0) ^ + (((residue >> 14) & 1) == 1 ? 0x1b27d4b5 : 0) ^ + (((residue >> 15) & 1) == 1 ? 0x04be1eb4 : 0) ^ + (((residue >> 16) & 1) == 1 ? 0x0968b861 : 0) ^ + (((residue >> 17) & 1) == 1 ? 0x1055f0c2 : 0) ^ + (((residue >> 18) & 1) == 1 ? 0x20ab4584 : 0) ^ + (((residue >> 19) & 1) == 1 ? 0x1342af08 : 0) ^ + (((residue >> 20) & 1) == 1 ? 0x24f1f318 : 0) ^ + (((residue >> 21) & 1) == 1 ? 0x1be34739 : 0) ^ + (((residue >> 22) & 1) == 1 ? 0x35562f7b : 0) ^ + (((residue >> 23) & 1) == 1 ? 0x3a3c5bff : 0) ^ + (((residue >> 24) & 1) == 1 ? 0x266c96f7 : 0) ^ + (((residue >> 25) & 1) == 1 ? 0x25c78b65 : 0) ^ + (((residue >> 26) & 1) == 1 ? 0x1b1f13ea : 0) ^ + (((residue >> 27) & 1) == 1 ? 0x34baa2f4 : 0) ^ + (((residue >> 28) & 1) == 1 ? 0x3b61c0e1 : 0) ^ + (((residue >> 29) & 1) == 1 ? 0x265325c2 : 0); + } + + private int[] locateErrors(int residue, int length) { + if (residue == 0) { + return new int[]{}; + } + + int syn = syndrome(residue); + int s0 = syn & 0x3FF; + int s1 = (syn >> 10) & 0x3FF; + int s2 = syn >> 20; + int l_s0 = GF1024_LOG[s0]; + int l_s1 = GF1024_LOG[s1]; + int l_s2 = GF1024_LOG[s2]; + + if (l_s0 != -1 && l_s1 != -1 && l_s2 != -1 && (2 * l_s1 - l_s2 - l_s0 + 2046) % 1023 == 0) { + int p1 = (l_s1 - l_s0 + 1023) % 1023; + if (p1 >= length) return new int[]{}; + int l_e1 = l_s0 + (1023 - 997) * p1; + if ((l_e1 % 33) == 1) return new int[]{}; + return new int[]{p1}; + } + + for (int p1 = 0; p1 < length; p1++) { + int s2_s1p1 = s2 ^ (s1 == 0 ? 0 : GF1024_EXP[(l_s1 + p1) % 1023]); + if (s2_s1p1 == 0) continue; + int s1_s0p1 = s1 ^ (s0 == 0 ? 0 : GF1024_EXP[(l_s0 + p1) % 1023]); + if (s1_s0p1 == 0) continue; + int l_s1_s0p1 = GF1024_LOG[s1_s0p1]; + int p2 = (GF1024_LOG[s2_s1p1] - l_s1_s0p1 + 1023) % 1023; + if (p2 >= length || p1 == p2) continue; + int s1_s0p2 = s1 ^ (s0 == 0 ? 0 : GF1024_EXP[(l_s0 + p2) % 1023]); + if (s1_s0p2 == 0) continue; + int inv_p1_p2 = 1023 - GF1024_LOG[GF1024_EXP[p1] ^ GF1024_EXP[p2]]; + int l_e2 = l_s1_s0p1 + inv_p1_p2 + (1023 - 997) * p2; + if ((l_e2 % 33) == 1) continue; + int l_e1 = GF1024_LOG[s1_s0p2] + inv_p1_p2 + (1023 - 997) * p1; + if ((l_e1 % 33) == 1) continue; + if (p1 < p2) { + return new int[]{p1, p2}; + } else { + return new int[]{p2, p1}; + } + } + return new int[]{}; + } + + private int polymod(int[] values) { + int chk = 1; + for (int value : values) { + int top = chk >> 25; + chk = (chk & 0x1ffffff) << 5 ^ value; + for (int i = 0; i < 5; ++i) { + if (((top >> i) & 1) == 1) { + chk ^= GENERATOR[i]; + } + } + } + return chk; + } + + private List hrpExpand(String hrp) { + List ret = new ArrayList<>(); + int p; + for (p = 0; p < hrp.length(); ++p) { + ret.add(Character.codePointAt(hrp, p) >> 5); + } + ret.add(0); + for (p = 0; p < hrp.length(); ++p) { + ret.add(Character.codePointAt(hrp, p) & 31); + } + return ret; + } + + public Bech32Decoded decode(String bechString) throws Bech32ValidationException { + if (bechString.length() > 90) { + throw new Bech32ValidationException.AddressLength("Too long"); + } + + int p; + boolean has_lower = false; + boolean has_upper = false; + for (p = 0; p < bechString.length(); ++p) { + if (Character.codePointAt(bechString, p) < 33 || Character.codePointAt(bechString, p) > 126) { + throw new Bech32ValidationException.InvalidCharacter(bechString.charAt(p), p); + } + if (bechString.charAt(p) >= 'a' && bechString.charAt(p) <= 'z') { + has_lower = true; + if (has_upper) throw new Bech32ValidationException.ContainsMixedCase(); + } + if (bechString.charAt(p) >= 'A' && bechString.charAt(p) <= 'Z') { + has_upper = true; + if (has_lower) throw new Bech32ValidationException.ContainsMixedCase(); + } + } + + bechString = bechString.toLowerCase(Locale.ROOT); + int pos = bechString.lastIndexOf('1'); + if (pos == -1) { + throw new Bech32ValidationException.InvalidSeparator(); + } + + if (pos < 1 || pos + 7 > bechString.length()) { + throw new Bech32ValidationException.InvalidSeparator(); + } + + String hrp = bechString.substring(0, pos); + List data = new ArrayList<>(); + for (p = pos + 1; p < bechString.length(); ++p) { + int d = CHARSET.indexOf(bechString.charAt(p)); + if (d == -1) { + throw new Bech32ValidationException.InvalidCharacter(bechString.charAt(p), p); + } + data.add(d); + } + + List expandedHrp = hrpExpand(hrp); + expandedHrp.addAll(data); + + int[] residueParams = listToByteArray(expandedHrp); + int residue = polymod(residueParams) ^ 1; + + if (residue != 0) { + int[] epos = locateErrors(residue, bechString.length() - 1); + if (epos.length == 0) { + throw new Bech32ValidationException("Invalid"); + } + for (int ep = 0; ep < epos.length; ++ep) { + epos[ep] = bechString.length() - epos[ep] - (epos[ep] >= data.size() ? 2 : 1); + } + throw new Bech32ValidationException("Invalid epos " + epos); + } + + int dataArrayLength = data.size(); + byte[] output = new byte[dataArrayLength]; + + for (int i = 0; i < dataArrayLength; i++) { + output[i] = data.get(i).byteValue(); + } + + return new Bech32Decoded(hrp, Arrays.copyOfRange(output, 0, output.length - 6)); + } + + private int[] listToByteArray(List list) { + int length = list.size(); + int[] output = new int[length]; + + for (int i = 0; i < length; i++) { + output[i] = list.get(i); + } + + return output; + } +} diff --git a/ref/java/src/main/java/com/conio/wallet/Bech32ValidationException.java b/ref/java/src/main/java/com/conio/wallet/Bech32ValidationException.java new file mode 100644 index 0000000..468b4d7 --- /dev/null +++ b/ref/java/src/main/java/com/conio/wallet/Bech32ValidationException.java @@ -0,0 +1,79 @@ +/* + * # Copyright (c) 2019 Lorenzo Zanotto + * # + * # 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. + */ + +package com.conio.wallet; + +public class Bech32ValidationException extends IllegalArgumentException { + Bech32ValidationException() { + super(); + } + + Bech32ValidationException(String message) { + super(message); + } + + static class InvalidCharacter extends Bech32ValidationException { + final char character; + final int position; + + InvalidCharacter(char character, int position) { + super("Invalid character '" + Character.toString(character) + "' at position " + position); + this.character = character; + this.position = position; + } + } + + static class ContainsMixedCase extends Bech32ValidationException { + + ContainsMixedCase() { + super("Mixed case"); + } + } + + static class InvalidHumanReadablePart extends Bech32ValidationException { + + InvalidHumanReadablePart() { + super("Invalid human-readable part"); + } + } + + static class InvalidSeparator extends Bech32ValidationException { + + InvalidSeparator() { + super("Missing, or placed in a wrong position, '1' separator"); + } + } + + static class AddressLength extends Bech32ValidationException { + + AddressLength(String message) { + super(message); + } + } + + static class IncorrectFormat extends Bech32ValidationException { + + IncorrectFormat(String message) { + super(message); + } + } +} diff --git a/ref/java/src/main/java/com/conio/wallet/SegwitAddress.java b/ref/java/src/main/java/com/conio/wallet/SegwitAddress.java new file mode 100644 index 0000000..0bb12aa --- /dev/null +++ b/ref/java/src/main/java/com/conio/wallet/SegwitAddress.java @@ -0,0 +1,152 @@ +/* + * # Copyright (c) 2019 Lorenzo Zanotto + * # + * # 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. + */ + +package com.conio.wallet; + +import com.conio.wallet.Bech32.Bech32Decoded; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class SegwitAddress { + + private int witnessVersion; + private List program; + + public SegwitAddress() { } + + private SegwitAddress(int witnessVersion, List program) { + this.witnessVersion = witnessVersion; + this.program = program; + } + + public int getWitnessVersion() { + return witnessVersion; + } + + public List getProgram() { + return program; + } + + /** + * Validates a Bech32 address with a given valid hrp. + * This version of the method is useful for checking whether + * an address is valid or not by analyzing the return value. + * If it's null, then the provided address with the corresponding + * hrp value cannot be validated + * + * @param address the Bech32 format address + * @param hrp the hrp to to test + * @return a payload containing the witness version and program + */ + public SegwitAddress decode(String address, String hrp) { + try { + return decodeThrowing(address, hrp); + } catch (Exception ignored) { + return null; + } + } + + /** + * Validates a Bech32 address with a given valid hrp. + * This version of the method is useful to know the + * reason that caused the validation to fail throwing + * a Bech32ValidationException that can be used to + * discriminate the various issues. + * + * @param address the Bech32 format address + * @param hrp the hrp to test + * @return a payload containing the witness version and program + * @throws Bech32ValidationException + */ + public SegwitAddress decodeThrowing(String address, String hrp) throws Bech32ValidationException { + if (address.length() < 14) { + throw new Bech32ValidationException.AddressLength("Too short"); + } + + if (address.length() > 74) { + throw new Bech32ValidationException.AddressLength("Too long"); + } + + if ((address.length() % 8) == 0 || (address.length() % 8) == 3 || (address.length() % 8) == 5) { + throw new Bech32ValidationException.AddressLength("Invalid length"); + } + + Bech32 bechAddress = new Bech32(); + Bech32Decoded dec = bechAddress.decode(address); + + byte[] data = Arrays.copyOfRange(dec.getData(), 1, dec.getData().length); + + List program = convertBits(data, 5, 8, false); + if (program == null) { + throw new Bech32ValidationException.IncorrectFormat("Padding error"); + } + + if (program.size() < 2 || program.size() > 40) { + throw new Bech32ValidationException.IncorrectFormat("Invalid witness program length"); + } + + if (dec.getData()[0] > 16) { + throw new Bech32ValidationException.IncorrectFormat("Invalid witness version"); + } + + if (dec.getData()[0] == 0 && program.size() != 20 && program.size() != 32) { + throw new Bech32ValidationException.IncorrectFormat("Invalid witness program length for v0"); + } + + if (!dec.getHrp().contains(hrp)) { + throw new Bech32ValidationException.InvalidHumanReadablePart(); + } + + return new SegwitAddress(dec.getData()[0], program); + } + + private List convertBits(byte[] data, int fromBits, int toBits, boolean pad) { + int acc = 0; + int bits = 0; + List ret = new ArrayList<>(); + int maxv = (1 << toBits) - 1; + + for (int value : data) { + if (value < 0 || (value >> fromBits) != 0) { + return null; + } + acc = (acc << fromBits) | value; + bits += fromBits; + while (bits >= toBits) { + bits -= toBits; + ret.add((acc >> bits) & maxv); + } + } + + if (pad) { + if (bits > 0) { + ret.add((acc << (toBits - bits)) & maxv); + } + } else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv) != 0) { + return null; + } + + return ret; + } +} + diff --git a/ref/java/src/test/java/Bech32ValidationTest.java b/ref/java/src/test/java/Bech32ValidationTest.java new file mode 100644 index 0000000..e1fddca --- /dev/null +++ b/ref/java/src/test/java/Bech32ValidationTest.java @@ -0,0 +1,136 @@ +/* + * # Copyright (c) 2019 Lorenzo Zanotto + * # + * # 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. + */ + +import com.conio.wallet.Bech32; +import com.conio.wallet.Bech32ValidationException; +import com.conio.wallet.SegwitAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; + +public class Bech32ValidationTest { + + // Classes Under Tests + + private SegwitAddress mSegwitAddress = new SegwitAddress(); + private Bech32 mBech32 = new Bech32(); + + // Checksums + + @Test + public void testValidChecksums() throws Bech32ValidationException { + String[] validChecksums = TestVectors.validChecksums; + int validChecksumCount = 0; + for (String checksum: validChecksums) { + mBech32.decode(checksum); + validChecksumCount++; + } + + boolean testPassed = validChecksumCount == validChecksums.length; + communicateResult("[Valid Checksum]", testPassed); + Assert.assertTrue(testPassed); + } + + @Test + public void testInvalidChecksum() { + String[] invalidChecksums = TestVectors.invalidChecksums; + int validChecksumCount = 0; + for (String checksum: invalidChecksums) { + try { + mBech32.decode(checksum); + validChecksumCount++; + } catch (Exception ignored) { } + } + + boolean testPassed = validChecksumCount == 0; + communicateResult("[Invalid Checksum]", testPassed); + Assert.assertTrue(testPassed); + } + + // Addresses + + @Test + public void testValidAddress() { + int index = 0; + for (String address: TestVectors.validAddress) { + String hrp = "bc"; + SegwitAddress decoded = mSegwitAddress.decode(address, hrp); + if (decoded == null) { + hrp = "tb"; + decoded = mSegwitAddress.decode(address, hrp); + } + + if (decoded != null) { + System.out.println("[Valid address] decoded witver: " + decoded.getWitnessVersion()); + } + + int[] expectedScriptPubKey = TestVectors.validAddressPubKeys[index]; + int[] scriptPubKey = scriptPubKey(decoded.getWitnessVersion(), decoded.getProgram()); + + boolean arePubKeysEqual = Arrays.equals(expectedScriptPubKey, scriptPubKey); + index++; + communicateResult("[Valid address " + address + " pub key match]", arePubKeysEqual); + Assert.assertTrue(arePubKeysEqual); + } + } + + @Test + public void testInvalidAddress() { + for (String address: TestVectors.invalidAddress) { + SegwitAddress main = mSegwitAddress.decode(address, "bc"); + if (main == null) communicateResult("[Invalid address " + address + " not decoded]", true); + SegwitAddress test = mSegwitAddress.decode(address, "tb"); + if (test == null) communicateResult("[Invalid address " + address + " not decoded]", true); + Assert.assertNull(main); + Assert.assertNull(test); + } + } + + // Script Pub Key + + private int[] scriptPubKey(int version, List program) { + List res = new ArrayList<>(); + res.add(version != 0 ? version + 0x50 : 0); + res.add(program.size()); + res.addAll(program); + + return listToByteArray(res); + } + + private int[] listToByteArray(List list) { + int length = list.size(); + int[] output = new int[length]; + + for (int i = 0; i < length; i++) { + output[i] = list.get(i); + } + + return output; + } + + private void communicateResult(String testName, boolean result) { + String returnValue = result ? "OK" : "FAILED"; + System.out.println(testName + " [" + returnValue + "]"); + } +} diff --git a/ref/java/src/test/java/TestVectors.java b/ref/java/src/test/java/TestVectors.java new file mode 100644 index 0000000..70d0b68 --- /dev/null +++ b/ref/java/src/test/java/TestVectors.java @@ -0,0 +1,115 @@ +/* + * # Copyright (c) 2019 Lorenzo Zanotto + * # + * # 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. + */ + +public class TestVectors { + + // HRPs + + static String MAINNETHRP = "bc"; + + static String TESTNETHRP = "tb"; + + static String[] allowedHrps = new String[]{TestVectors.MAINNETHRP, TestVectors.TESTNETHRP}; + + // Checksums + + static String[] validChecksums = new String[] { + "A12UEL5L", + "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", + "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", + "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", + }; + + static String[] invalidChecksums = new String[] { + " 1nwldj5", + "\\x7F" + "1axkwrx", + "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", + "pzry9x0s0muk", + "1pzry9x0s0muk", + "x1b4n0q5v", + "li1dgmt3", + "de1lg7wt\\xFF" + }; + + // Addresses + + static String[] validAddress = new String[] { + "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", + "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", + "BC1SW50QA3JX3S", + "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", + "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy" + }; + + static int[][] validAddressPubKeys = new int[][] { + { + // BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4 + 0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, + 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 + }, + { + // tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7 + 0x00, 0x20, 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, 0x04, + 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, 0x6c, 0x98, 0x56, 0x78, + 0xcd, 0x4d, 0x27, 0xa1, 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, + 0x62 + }, + { + // bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx + 0x51, 0x28, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, + 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6, + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, + 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 + }, + { + // BC1SW50QA3JX3S + 0x60, 0x02, 0x75, 0x1e + }, + { + // bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj + 0x52, 0x10, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, + 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23 + }, + { + // tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy + 0x00, 0x20, 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21, + 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, 0x36, 0x2b, 0x99, 0xd5, + 0xe9, 0x1c, 0x6c, 0xe2, 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, + 0x33 + } + }; + + static String[] invalidAddress = new String[] { + "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", + "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", + "bc1rw5uspcuh", + "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", + "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", + "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", + "bc1gmk9yu" + }; +}