mirror of
https://github.com/sparrowwallet/hummingbird.git
synced 2024-12-26 10:06:45 +00:00
add legacy (ur1.0) encoding and decoding
This commit is contained in:
parent
a263d00e03
commit
71300aa97d
6 changed files with 556 additions and 0 deletions
171
src/main/java/com/sparrowwallet/hummingbird/BC32.java
Normal file
171
src/main/java/com/sparrowwallet/hummingbird/BC32.java
Normal file
|
@ -0,0 +1,171 @@
|
|||
package com.sparrowwallet.hummingbird;
|
||||
|
||||
/*
|
||||
* Copyright 2018 Coinomi Ltd
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class BC32 {
|
||||
/**
|
||||
* The BC32 character set for encoding.
|
||||
*/
|
||||
private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||
|
||||
/**
|
||||
* Find the polynomial with value coefficients mod the generator as 30-bit.
|
||||
*/
|
||||
private static int polymod(final byte[] values) {
|
||||
int c = 1;
|
||||
for (byte v_i : values) {
|
||||
int c0 = (c >>> 25) & 0xff;
|
||||
c = ((c & 0x1ffffff) << 5) ^ (v_i & 0xff);
|
||||
if ((c0 & 1) != 0) c ^= 0x3b6a57b2;
|
||||
if ((c0 & 2) != 0) c ^= 0x26508e6d;
|
||||
if ((c0 & 4) != 0) c ^= 0x1ea119fa;
|
||||
if ((c0 & 8) != 0) c ^= 0x3d4233dd;
|
||||
if ((c0 & 16) != 0) c ^= 0x2a1462b3;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a checksum.
|
||||
*/
|
||||
private static boolean verifyChecksum(final byte[] values) {
|
||||
byte[] combined = new byte[1 + values.length];
|
||||
System.arraycopy(new byte[]{0}, 0, combined, 0, 1);
|
||||
System.arraycopy(values, 0, combined, 1, values.length);
|
||||
return polymod(combined) == 0x3fffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a checksum.
|
||||
*/
|
||||
private static byte[] createChecksum(final byte[] values) {
|
||||
byte[] enc = new byte[1 + values.length + 6];
|
||||
System.arraycopy(values, 0, enc, 1, values.length);
|
||||
int mod = polymod(enc) ^ 0x3fffffff;
|
||||
byte[] result = new byte[6];
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
result[i] = (byte) ((mod >>> (5 * (5 - i))) & 31);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a BC32 string.
|
||||
*/
|
||||
public static String encode(final byte[] values) {
|
||||
List<Byte> boxedList = new ArrayList<>(values.length);
|
||||
for(byte value : values) {
|
||||
boxedList.add(value);
|
||||
}
|
||||
|
||||
byte[] data = convertBits(boxedList, 8, 5, true);
|
||||
|
||||
byte[] checksum = createChecksum(data);
|
||||
byte[] combined = new byte[data.length + checksum.length];
|
||||
System.arraycopy(data, 0, combined, 0, data.length);
|
||||
System.arraycopy(checksum, 0, combined, data.length, checksum.length);
|
||||
|
||||
StringBuilder sb = new StringBuilder(combined.length);
|
||||
for (byte b : combined) {
|
||||
sb.append(CHARSET.charAt(b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a BC32 string.
|
||||
*/
|
||||
public static byte[] decode(final String str) {
|
||||
boolean lower = false, upper = false;
|
||||
if (str.length() < 6)
|
||||
throw new BC32DecoderException.InvalidDataLength("Input too short: " + str.length());
|
||||
for (int i = 0; i < str.length(); ++i) {
|
||||
char c = str.charAt(i);
|
||||
if (c < 33 || c > 126) throw new BC32DecoderException.InvalidCharacter(c, i);
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
if (upper)
|
||||
throw new BC32DecoderException.InvalidCharacter(c, i);
|
||||
lower = true;
|
||||
}
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
if (lower)
|
||||
throw new BC32DecoderException.InvalidCharacter(c, i);
|
||||
upper = true;
|
||||
}
|
||||
}
|
||||
|
||||
final int dataPartLength = str.length();
|
||||
byte[] values = new byte[dataPartLength];
|
||||
for (int i = 0; i < dataPartLength; ++i) {
|
||||
char c = str.charAt(i);
|
||||
|
||||
if(CHARSET.indexOf(c) == -1) {
|
||||
throw new IllegalArgumentException("BC32 characters out of range");
|
||||
}
|
||||
values[i] = (byte) CHARSET.indexOf(c);
|
||||
}
|
||||
|
||||
if (!verifyChecksum(values)) throw new BC32DecoderException.InvalidChecksum();
|
||||
|
||||
byte[] data = Arrays.copyOfRange(values, 0, values.length - 6);
|
||||
List<Byte> valueList = new ArrayList<>();
|
||||
for (byte b : data) {
|
||||
valueList.add(b);
|
||||
}
|
||||
return convertBits(valueList, 5, 8, false);
|
||||
}
|
||||
|
||||
private static byte[] convertBits(List<Byte> data, int fromBits, int toBits, boolean pad) {
|
||||
int acc = 0;
|
||||
int bits = 0;
|
||||
int maxv = (1 << toBits) - 1;
|
||||
List<Byte> ret = new ArrayList<>();
|
||||
|
||||
for (Byte value : data) {
|
||||
short b = (short) (value & 0xff);
|
||||
if ((b >> fromBits) > 0) {
|
||||
throw new IllegalArgumentException("Illegal bytes for bc32 encoding");
|
||||
}
|
||||
|
||||
acc = (acc << fromBits) | b;
|
||||
bits += fromBits;
|
||||
while (bits >= toBits) {
|
||||
bits -= toBits;
|
||||
ret.add((byte) ((acc >> bits) & maxv));
|
||||
}
|
||||
}
|
||||
|
||||
if (pad && (bits > 0)) {
|
||||
ret.add((byte) ((acc << (toBits - bits)) & maxv));
|
||||
} else if (bits >= fromBits || (byte) (((acc << (toBits - bits)) & maxv)) != 0) {
|
||||
throw new IllegalArgumentException("Illegal bytes for bc32 encoding");
|
||||
}
|
||||
|
||||
Object[] boxedArray = ret.toArray();
|
||||
int len = boxedArray.length;
|
||||
byte[] array = new byte[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
array[i] = ((Number)boxedArray[i]).byteValue();
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.sparrowwallet.hummingbird;
|
||||
|
||||
public class BC32DecoderException extends IllegalArgumentException {
|
||||
|
||||
public BC32DecoderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public static class InvalidCharacter extends BC32DecoderException {
|
||||
public final char character;
|
||||
public final int position;
|
||||
|
||||
public InvalidCharacter(char character, int position) {
|
||||
super("Invalid character '" + character + "' at position " + position);
|
||||
this.character = character;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidDataLength extends BC32DecoderException {
|
||||
|
||||
public InvalidDataLength(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidChecksum extends BC32DecoderException {
|
||||
public InvalidChecksum() {
|
||||
super("Checksum does not validate");
|
||||
}
|
||||
}
|
||||
}
|
146
src/main/java/com/sparrowwallet/hummingbird/LegacyURDecoder.java
Normal file
146
src/main/java/com/sparrowwallet/hummingbird/LegacyURDecoder.java
Normal file
|
@ -0,0 +1,146 @@
|
|||
package com.sparrowwallet.hummingbird;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.*;
|
||||
|
||||
public class LegacyURDecoder {
|
||||
private final Set<String> fragments = new LinkedHashSet<>();
|
||||
|
||||
public void receivePart(String fragment) {
|
||||
fragments.add(fragment);
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
if(fragments.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String fragment = fragments.iterator().next();
|
||||
String[] components = fragment.split("/");
|
||||
if(components.length > 3) {
|
||||
int[] sequence = checkAndGetSequence(components[1]);
|
||||
int total = sequence[1];
|
||||
return total == fragments.size();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isLegacyURFragment(String fragment) {
|
||||
String[] components = fragment.split("/");
|
||||
|
||||
//Multi-part legacy encoding may include digest which adds an extra component
|
||||
if(components.length > 3) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Last component is always fragment payload in both legacy and current
|
||||
String payload = components[components.length-1];
|
||||
|
||||
//BC32 will never contain the following characters
|
||||
if(payload.indexOf('b') > -1 || payload.indexOf('i') > -1 || payload.indexOf('o') > -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Bytewords and BC32 strings can usually be distinguished because the latter includes digits as permissible characters
|
||||
return (payload.matches(".*\\d.*"));
|
||||
}
|
||||
|
||||
public UR decode() throws UR.InvalidTypeException {
|
||||
return decode(fragments.toArray(new String[0]));
|
||||
}
|
||||
|
||||
public static UR decode(String[] fragments) throws UR.InvalidTypeException {
|
||||
int length = fragments.length;
|
||||
if(length == 1){
|
||||
return handleFragment(fragments[0]);
|
||||
}
|
||||
else {
|
||||
return handleFragments(fragments);
|
||||
}
|
||||
}
|
||||
|
||||
private static UR handleFragment(String fragment) throws UR.InvalidTypeException {
|
||||
String[] components = fragment.split("/");
|
||||
|
||||
switch(components.length) {
|
||||
case 2 -> {
|
||||
return new UR(components[0], BC32.decode(components[1]));
|
||||
}
|
||||
case 3 -> {
|
||||
String digest = components[1];
|
||||
String data = components[2];
|
||||
checkDigest(data, digest);
|
||||
return new UR(components[0], BC32.decode(data));
|
||||
}
|
||||
case 4 -> {
|
||||
checkAndGetSequence(components[1]);
|
||||
String digest = components[2];
|
||||
String data = components[3];
|
||||
checkDigest(digest, fragment);
|
||||
return new UR(components[0], BC32.decode(data));
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Invalid number of fragments: expected 2 / 3 / 4 but got " + components.length);
|
||||
}
|
||||
}
|
||||
|
||||
private static UR handleFragments(String[] fragments) throws UR.InvalidTypeException {
|
||||
int length = fragments.length;
|
||||
String[] parts = new String[length];
|
||||
Arrays.fill(parts, "");
|
||||
String type = null;
|
||||
String digest = null;
|
||||
for(String fragment : fragments) {
|
||||
String[] components = fragment.split("/");
|
||||
if(components.length < 4) {
|
||||
throw new IllegalArgumentException(String.format("Invalid fragment: %s, insufficient number of components (%d)", fragment, components.length));
|
||||
}
|
||||
if(type != null && !type.equals(components[0])) {
|
||||
throw new IllegalArgumentException(String.format("Invalid fragment: %s, checksum changed %s, %s", fragment, type, components[0]));
|
||||
}
|
||||
type = components[0];
|
||||
int[] sequence = checkAndGetSequence(components[1]);
|
||||
int index = sequence[0];
|
||||
int total = sequence[1];
|
||||
if(total != length) {
|
||||
throw new IllegalArgumentException(String.format("Invalid fragment: %s, total %d not equals to fragments length %d", fragment, total, length));
|
||||
}
|
||||
if(digest != null && !digest.equals(components[2])) {
|
||||
throw new IllegalArgumentException(String.format("Invalid fragment: %s, checksum changed %s, %s", fragment, digest, components[2]));
|
||||
}
|
||||
digest = components[2];
|
||||
if(parts[index - 1].length() > 0) {
|
||||
throw new IllegalArgumentException(String.format("Invalid fragment: %s, index %d has already been set", fragment, index));
|
||||
}
|
||||
parts[index - 1] = components[3];
|
||||
}
|
||||
String payload = Arrays.stream(parts).reduce((cur, acc) -> cur+acc).orElse("");
|
||||
checkDigest(payload, digest);
|
||||
|
||||
return new UR(type, BC32.decode(payload));
|
||||
}
|
||||
|
||||
private static void checkDigest(String payload, String digest) {
|
||||
MessageDigest sha256Digest = LegacyUREncoder.newDigest();
|
||||
sha256Digest.update(BC32.decode(payload));
|
||||
byte[] calculatedChecksum = sha256Digest.digest();
|
||||
byte[] checksum = BC32.decode(digest);
|
||||
|
||||
if(!Arrays.equals(calculatedChecksum, checksum)) {
|
||||
throw new IllegalArgumentException("Invalid digest: " + digest + " for payload: " + payload);
|
||||
}
|
||||
}
|
||||
|
||||
public static int[] checkAndGetSequence(String payload) {
|
||||
String[] pieces = payload.toLowerCase().split("of");
|
||||
if(pieces.length != 2) {
|
||||
throw new IllegalArgumentException("Invalid sequence: " + payload);
|
||||
}
|
||||
int index = Integer.parseInt(pieces[0]);
|
||||
int total = Integer.parseInt(pieces[1]);
|
||||
if(index < 1 || index > total) {
|
||||
throw new IllegalArgumentException("Invalid sequence: " + payload);
|
||||
}
|
||||
return new int[]{index, total};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package com.sparrowwallet.hummingbird;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class LegacyUREncoder {
|
||||
public static final int DEFAULT_FRAGMENT_LENGTH = 200;
|
||||
|
||||
private final UR ur;
|
||||
private final int fragmentLen;
|
||||
|
||||
public LegacyUREncoder(UR ur) {
|
||||
this(ur, DEFAULT_FRAGMENT_LENGTH);
|
||||
}
|
||||
|
||||
public LegacyUREncoder(UR ur, int fragmentLen) {
|
||||
this.ur = ur;
|
||||
this.fragmentLen = fragmentLen;
|
||||
}
|
||||
|
||||
public String[] encode() {
|
||||
String encoded = BC32.encode(ur.getCborBytes());
|
||||
|
||||
MessageDigest sha256Digest = newDigest();
|
||||
sha256Digest.update(ur.getCborBytes());
|
||||
byte[] checksum = sha256Digest.digest();
|
||||
String bc32Checksum = BC32.encode(checksum);
|
||||
|
||||
String[] fragments = splitData(encoded, fragmentLen);
|
||||
return composeHeadersToFragments(fragments, bc32Checksum);
|
||||
}
|
||||
|
||||
private String[] splitData(String s, int fragmentLen) {
|
||||
int count = (int)Math.ceil(s.length() / (float)fragmentLen);
|
||||
int partLength = (int)Math.ceil(s.length() / (float)count);
|
||||
String[] fragments = new String[count];
|
||||
for(int i = 0; i < count; i++) {
|
||||
fragments[i] = s.substring(partLength * i, Math.min(partLength * (i + 1), s.length()));
|
||||
}
|
||||
|
||||
return fragments;
|
||||
}
|
||||
|
||||
private String[] composeHeadersToFragments(String[] fragments, String checksum) {
|
||||
int length = fragments.length;
|
||||
if(length <= 1) {
|
||||
return Arrays.stream(fragments).map(this::composeUR).toArray(String[]::new);
|
||||
} else {
|
||||
return IntStream.range(0, length)
|
||||
.mapToObj(i -> composeHeadersToFragment(fragments[i], checksum, i, length))
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
public static MessageDigest newDigest() {
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e); // Can't happen.
|
||||
}
|
||||
}
|
||||
|
||||
public String composeHeadersToFragment(String fragment, String checksum, int index, int total) {
|
||||
return composeUR(composeSequencing(composeChecksum(fragment, checksum), index, total));
|
||||
}
|
||||
|
||||
private String composeChecksum(String payload, String checksum) {
|
||||
return String.format("%s/%s", checksum, payload);
|
||||
}
|
||||
|
||||
private String composeSequencing(String payload, int index, int total) {
|
||||
return String.format("%dof%d/%s", index + 1, total, payload);
|
||||
}
|
||||
|
||||
private String composeUR(String payload) {
|
||||
return composeUR(payload, ur.getType());
|
||||
}
|
||||
|
||||
private String composeUR(String payload, String type) {
|
||||
return String.format("ur:%s/%s", type, payload);
|
||||
}
|
||||
}
|
20
src/test/java/com/sparrowwallet/hummingbird/BC32Test.java
Normal file
20
src/test/java/com/sparrowwallet/hummingbird/BC32Test.java
Normal file
|
@ -0,0 +1,20 @@
|
|||
package com.sparrowwallet.hummingbird;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class BC32Test {
|
||||
@Test
|
||||
public void testDecode() {
|
||||
byte[] decode = BC32.decode("fpjkcmr0ypmk7unvvsh4ra4j");
|
||||
Assert.assertEquals("Hello world", new String(decode));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncode() {
|
||||
String s = BC32.encode("Hello world".getBytes(StandardCharsets.UTF_8));
|
||||
Assert.assertEquals("fpjkcmr0ypmk7unvvsh4ra4j", s);
|
||||
}
|
||||
}
|
|
@ -7,6 +7,8 @@ import org.junit.Assert;
|
|||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
@ -17,6 +19,7 @@ public class URTest {
|
|||
UR ur = makeMessageUR(50, "Wolf");
|
||||
String encoded = UREncoder.encode(ur);
|
||||
Assert.assertEquals("ur:bytes/hdeymejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtgwdpfnsboxgwlbaawzuefywkdplrsrjynbvygabwjldapfcsdwkbrkch", encoded);
|
||||
Assert.assertFalse(LegacyURDecoder.isLegacyURFragment(encoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -47,6 +50,7 @@ public class URTest {
|
|||
"ur:bytes/20-9/lpbbascfadaxcywenbpljkhdcayapmrleeleaxpasfrtrdkncffwjyjzgyetdmlewtkpktgllepfrltataztksmhkbot"
|
||||
};
|
||||
Assert.assertArrayEquals("", expectedParts, parts.toArray());
|
||||
parts.forEach(part -> Assert.assertFalse(LegacyURDecoder.isLegacyURFragment(part)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -58,6 +62,7 @@ public class URTest {
|
|||
|
||||
do {
|
||||
String part = urEncoder.nextPart();
|
||||
Assert.assertFalse(LegacyURDecoder.isLegacyURFragment(part));
|
||||
urDecoder.receivePart(part);
|
||||
} while(urDecoder.getResult() == null);
|
||||
|
||||
|
@ -103,4 +108,102 @@ public class URTest {
|
|||
String encoded = UREncoder.encode(ur);
|
||||
Assert.assertEquals("ur:bytes/gdaebycpeofygoiyktlonlpkrksfutwyzmwmfyeozs", encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyEncode() throws UR.InvalidTypeException, UR.InvalidCBORException {
|
||||
String random = "7e4a61385e2550981b4b5633ab178eb077a30505fbd53f107ec1081e7cf0ca3c0dc0bfea5b8bfb5e6ffc91afd104c3aa756210b5dbc5118fd12c87ee04269815ba6a9968a0d0d3b7a9b631382a36bc70ab626d5670b4b48ff843f4d9a15631aa67c7aaf0ac6ce7e3bff03b2c9643e3375e47493c4e0f8635329d66fdec41b10ce74dcbf25fc15d829e7830c325643a98561f441b40a02e8353493e6afc16192fe99d90d8ca65539af77ddeaccc8943a37563a9ba83675bd5d4da7c60c9a172cf6940cbf0ec8fe04175a629932e3512c5d2aaea3cca3246f40a21ffdc33c3987dc7b880351230eb3759fe3c7dc7b2d3a20a95996ff0b7a0dba834f96beb64c14e3426fb051a936ba41569ab99c0066a6d9c0777a49e49e6cbad24d722a4c7da112432679264b9adc0a8cff9dd1fe0ee9ee2747f6a68537c389a7303a1af23c534ee6392bc17b04cf0fbce7689e66b673a440c04a9454005b0c76664639113458eb7d0902eff04d11138ce2a8ee16a9cd7c8926514efa9bd83ae7a4c139835f0fe0f68c628e0645c8524c30dfc314e825a7aa13224d98e2f7a9d12183a999bb1f28549c99a9072d99c05c24e0c84848c4fc147a094ab7b69e9cbea86952fccf15500fbb234ffe6ee6e6ded515c8016cb017ba36fb931ef276cec4ed22c1aed1495d2df3b3ce66c03f5b9ffa8434bf0e8fb149de94e050b3da178df1f76c00a366cb2801fabdf1a1e90cd3cd45ecb7a930a40b151455f76b726d552f31c21324992da257ff8bde2923dfd5d0d6b87233fae215ffacbecd96249099e7e3427d533db56cdb09c7475b4ce3314e33f43953a7370866cc11d85f00b71b15510b46c4b4fa490c660ddfeda0ceb1b8265995f7071c155ad1b57465fdc0fa81a73f9f19ac4872029d5844c1838f732e803043673e26cbc5b51297a324ff00a2d2d4222bad556b93d27c8e376e3ff8f9d37b3073410708ebb3d4dd7473d27212310b71a3c33a5c8f87f44824640e7f8970f4eda9364195c87a91172b8f085f1773641dde1ce21938746234055bc971ce2325f814e3eec60f781dd4faf52afd5be4a6b38656f7e9739f724cb7ccd4e4d01e802add3dc7b83191f894b3ee0ed752ee514d5ec55";
|
||||
|
||||
UR ur = UR.fromBytes(TestUtils.hexToBytes(random));
|
||||
LegacyUREncoder legacyUREncoder = new LegacyUREncoder(ur);
|
||||
String[] result = legacyUREncoder.encode();
|
||||
Assert.assertArrayEquals(result, new String[]{
|
||||
"ur:bytes/1of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/typjqlj2vyu9uf2snqd5k43n4vtcavrh5vzst7748ug8asggre70pj3uphqtl6jm30a4umlujxhazpxr4f6kyy94m0z3rr739jr7uppxnq2m565edzsdp5ah4xmrzwp2x678p2mzd4t8pd953luy8axe59trr2n8c740ptrvul3mlupm9jty8ceht",
|
||||
"ur:bytes/2of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/er5j0zwp7rr2v5avm77csd3pnn5mjljtlq4mq570qcvxfty82v9v86yrdq2qt5r2dynu6huzcvjl6vajrvv5e2nntmhmh4vejy58gm4vw5m4qm8t02afknuvry6zuk0d9qvhu8v3lsyzadx9xfjudgjchf2463uegeydaq2y8lacv7rnp7u0wyqx5",
|
||||
"ur:bytes/3of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/frp6eht8lrclw8ktf6yz54n9hlpdaqmw5rf7ttadjvzn35ymas2x5ndwjp26dtn8qqv6ndnsrh0fy7f8nvhtfy6u32f376zyjryeujvju6ms9gelua68lqa60wyarldf59xlpcnfes8gd0y0znfmnrj27p0vzv7rauua5fue4kwwjypsz2j32qqkc",
|
||||
"ur:bytes/4of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/vwenyvwg3x3vwklgfqthlqng3zwxw928wz65u6lyfyeg5a75mmqaw0fxp8xp47rlq76xx9rsxghy9ynpsmlp3f6p9574pxgjdnr3002w3yxp6nxdmru59f8ye4yrjmxwqtsjwpjzgfrz0c9r6p99t0d57njl2s62jln8325q0hv35llnwumnda4g4",
|
||||
"ur:bytes/5of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/eqqkevqhhgm0hyc77fmva38dytq6a52ft5kl8v7wvmqr7kull2zrf0cw37c5nh55upgt8ksh3hclwmqq5dnvk2qpl27lrg0fpnfu630vk75npfqtz529tamtwfk42te3cgfjfxfd5ftllz779y3al4ws66u8yvl6ug2llt97ektzfyyeul35yl2n8",
|
||||
"ur:bytes/6of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/k6kekcfcar4kn8rx98r8ape2wnnwzrxesgashcqkud325gtgmztf7jfp3nqmhld5r8trwpxtx2lwpcuz4ddrdt5vh7up75p5ule7xdvfpeq982cgnqc8rmn96qrqsm88cnvh3d4z2t6xf8lqz3d94pz9wk426un6f7gudmw8lu0n5mmxpe5zpcgaw",
|
||||
"ur:bytes/7of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/eafht5w0f8yy33pdc68se6tj8c0azgy3jqulufwr6wm2fkgx2us753zu4c7zzlzaekg8w7rn3pjwr5vg6q2k7fw88zxf0czn37a3s00qwaf7h49t74he9xkwr9dalfww0hyn9hen2wf5q7sq4d60w8hqcer7y5k0hqa46jaeg56hk92xd0vz4",
|
||||
});
|
||||
|
||||
Assert.assertEquals(random, TestUtils.bytesToHex(LegacyURDecoder.decode(result).toBytes()));
|
||||
Arrays.stream(result).forEach(part -> Assert.assertTrue(LegacyURDecoder.isLegacyURFragment(part)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyEncodeShort() throws Exception {
|
||||
String random = "7e4a61385e2550981b4b5633ab178eb077a30505fb";
|
||||
|
||||
UR ur = UR.fromBytes(TestUtils.hexToBytes(random));
|
||||
LegacyUREncoder legacyUREncoder = new LegacyUREncoder(ur);
|
||||
String[] result = legacyUREncoder.encode();
|
||||
|
||||
Assert.assertArrayEquals(result, new String[] {
|
||||
"ur:bytes/24ly5cfctcj4pxqmfdtr82ch36c80gc9qhasmxdxs3"
|
||||
});
|
||||
Assert.assertTrue(LegacyURDecoder.isLegacyURFragment(result[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyDecode() throws Exception{
|
||||
String[] fragments = new String[]{
|
||||
"ur:bytes/1of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/typjqlj2vyu9uf2snqd5k43n4vtcavrh5vzst7748ug8asggre70pj3uphqtl6jm30a4umlujxhazpxr4f6kyy94m0z3rr739jr7uppxnq2m565edzsdp5ah4xmrzwp2x678p2mzd4t8pd953luy8axe59trr2n8c740ptrvul3mlupm9jty8cehter5j0zwp7rr2v5a",
|
||||
"ur:bytes/2of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/vm77csd3pnn5mjljtlq4mq570qcvxfty82v9v86yrdq2qt5r2dynu6huzcvjl6vajrvv5e2nntmhmh4vejy58gm4vw5m4qm8t02afknuvry6zuk0d9qvhu8v3lsyzadx9xfjudgjchf2463uegeydaq2y8lacv7rnp7u0wyqx5frp6eht8lrclw8ktf6yz54n9hlpdaq",
|
||||
"ur:bytes/3of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/mw5rf7ttadjvzn35ymas2x5ndwjp26dtn8qqv6ndnsrh0fy7f8nvhtfy6u32f376zyjryeujvju6ms9gelua68lqa60wyarldf59xlpcnfes8gd0y0znfmnrj27p0vzv7rauua5fue4kwwjypsz2j32qqkcvwenyvwg3x3vwklgfqthlqng3zwxw928wz65u6lyfyeg5",
|
||||
"ur:bytes/4of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/a75mmqaw0fxp8xp47rlq76xx9rsxghy9ynpsmlp3f6p9574pxgjdnr3002w3yxp6nxdmru59f8ye4yrjmxwqtsjwpjzgfrz0c9r6p99t0d57njl2s62jln8325q0hv35llnwumnda4g4eqqkevqhhgm0hyc77fmva38dytq6a52ft5kl8v7wvmqr7kull2zrf0cw37c5",
|
||||
"ur:bytes/5of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/nh55upgt8ksh3hclwmqq5dnvk2qpl27lrg0fpnfu630vk75npfqtz529tamtwfk42te3cgfjfxfd5ftllz779y3al4ws66u8yvl6ug2llt97ektzfyyeul35yl2n8k6kekcfcar4kn8rx98r8ape2wnnwzrxesgashcqkud325gtgmztf7jfp3nqmhld5r8trwpxtx2l",
|
||||
"ur:bytes/6of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/wpcuz4ddrdt5vh7up75p5ule7xdvfpeq982cgnqc8rmn96qrqsm88cnvh3d4z2t6xf8lqz3d94pz9wk426un6f7gudmw8lu0n5mmxpe5zpcgaweafht5w0f8yy33pdc68se6tj8c0azgy3jqulufwr6wm2fkgx2us753zu4c7zzlzaekg8w7rn3pjwr5vg6q2k7fw88z",
|
||||
"ur:bytes/7of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/xf0czn37a3s00qwaf7h49t74he9xkwr9dalfww0hyn9hen2wf5q7sq4d60w8hqcer7y5k0hqa46jaeg56hk92xd0vz4",
|
||||
};
|
||||
|
||||
UR ur = LegacyURDecoder.decode(fragments);
|
||||
Assert.assertEquals(TestUtils.bytesToHex(ur.toBytes()), "7e4a61385e2550981b4b5633ab178eb077a30505fbd53f107ec1081e7cf0ca3c0dc0bfea5b8bfb5e6ffc91afd104c3aa756210b5dbc5118fd12c87ee04269815ba6a9968a0d0d3b7a9b631382a36bc70ab626d5670b4b48ff843f4d9a15631aa67c7aaf0ac6ce7e3bff03b2c9643e3375e47493c4e0f8635329d66fdec41b10ce74dcbf25fc15d829e7830c325643a98561f441b40a02e8353493e6afc16192fe99d90d8ca65539af77ddeaccc8943a37563a9ba83675bd5d4da7c60c9a172cf6940cbf0ec8fe04175a629932e3512c5d2aaea3cca3246f40a21ffdc33c3987dc7b880351230eb3759fe3c7dc7b2d3a20a95996ff0b7a0dba834f96beb64c14e3426fb051a936ba41569ab99c0066a6d9c0777a49e49e6cbad24d722a4c7da112432679264b9adc0a8cff9dd1fe0ee9ee2747f6a68537c389a7303a1af23c534ee6392bc17b04cf0fbce7689e66b673a440c04a9454005b0c76664639113458eb7d0902eff04d11138ce2a8ee16a9cd7c8926514efa9bd83ae7a4c139835f0fe0f68c628e0645c8524c30dfc314e825a7aa13224d98e2f7a9d12183a999bb1f28549c99a9072d99c05c24e0c84848c4fc147a094ab7b69e9cbea86952fccf15500fbb234ffe6ee6e6ded515c8016cb017ba36fb931ef276cec4ed22c1aed1495d2df3b3ce66c03f5b9ffa8434bf0e8fb149de94e050b3da178df1f76c00a366cb2801fabdf1a1e90cd3cd45ecb7a930a40b151455f76b726d552f31c21324992da257ff8bde2923dfd5d0d6b87233fae215ffacbecd96249099e7e3427d533db56cdb09c7475b4ce3314e33f43953a7370866cc11d85f00b71b15510b46c4b4fa490c660ddfeda0ceb1b8265995f7071c155ad1b57465fdc0fa81a73f9f19ac4872029d5844c1838f732e803043673e26cbc5b51297a324ff00a2d2d4222bad556b93d27c8e376e3ff8f9d37b3073410708ebb3d4dd7473d27212310b71a3c33a5c8f87f44824640e7f8970f4eda9364195c87a91172b8f085f1773641dde1ce21938746234055bc971ce2325f814e3eec60f781dd4faf52afd5be4a6b38656f7e9739f724cb7ccd4e4d01e802add3dc7b83191f894b3ee0ed752ee514d5ec55");
|
||||
Arrays.stream(fragments).forEach(part -> Assert.assertTrue(LegacyURDecoder.isLegacyURFragment(part)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyDecodeParts() throws Exception{
|
||||
String[] fragments = new String[]{
|
||||
"ur:bytes/1of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/typjqlj2vyu9uf2snqd5k43n4vtcavrh5vzst7748ug8asggre70pj3uphqtl6jm30a4umlujxhazpxr4f6kyy94m0z3rr739jr7uppxnq2m565edzsdp5ah4xmrzwp2x678p2mzd4t8pd953luy8axe59trr2n8c740ptrvul3mlupm9jty8cehter5j0zwp7rr2v5a",
|
||||
"ur:bytes/2of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/vm77csd3pnn5mjljtlq4mq570qcvxfty82v9v86yrdq2qt5r2dynu6huzcvjl6vajrvv5e2nntmhmh4vejy58gm4vw5m4qm8t02afknuvry6zuk0d9qvhu8v3lsyzadx9xfjudgjchf2463uegeydaq2y8lacv7rnp7u0wyqx5frp6eht8lrclw8ktf6yz54n9hlpdaq",
|
||||
"ur:bytes/3of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/mw5rf7ttadjvzn35ymas2x5ndwjp26dtn8qqv6ndnsrh0fy7f8nvhtfy6u32f376zyjryeujvju6ms9gelua68lqa60wyarldf59xlpcnfes8gd0y0znfmnrj27p0vzv7rauua5fue4kwwjypsz2j32qqkcvwenyvwg3x3vwklgfqthlqng3zwxw928wz65u6lyfyeg5",
|
||||
"ur:bytes/4of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/a75mmqaw0fxp8xp47rlq76xx9rsxghy9ynpsmlp3f6p9574pxgjdnr3002w3yxp6nxdmru59f8ye4yrjmxwqtsjwpjzgfrz0c9r6p99t0d57njl2s62jln8325q0hv35llnwumnda4g4eqqkevqhhgm0hyc77fmva38dytq6a52ft5kl8v7wvmqr7kull2zrf0cw37c5",
|
||||
"ur:bytes/5of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/nh55upgt8ksh3hclwmqq5dnvk2qpl27lrg0fpnfu630vk75npfqtz529tamtwfk42te3cgfjfxfd5ftllz779y3al4ws66u8yvl6ug2llt97ektzfyyeul35yl2n8k6kekcfcar4kn8rx98r8ape2wnnwzrxesgashcqkud325gtgmztf7jfp3nqmhld5r8trwpxtx2l",
|
||||
"ur:bytes/6of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/wpcuz4ddrdt5vh7up75p5ule7xdvfpeq982cgnqc8rmn96qrqsm88cnvh3d4z2t6xf8lqz3d94pz9wk426un6f7gudmw8lu0n5mmxpe5zpcgaweafht5w0f8yy33pdc68se6tj8c0azgy3jqulufwr6wm2fkgx2us753zu4c7zzlzaekg8w7rn3pjwr5vg6q2k7fw88z",
|
||||
"ur:bytes/7of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/xf0czn37a3s00qwaf7h49t74he9xkwr9dalfww0hyn9hen2wf5q7sq4d60w8hqcer7y5k0hqa46jaeg56hk92xd0vz4",
|
||||
};
|
||||
|
||||
LegacyURDecoder legacyURDecoder = new LegacyURDecoder();
|
||||
|
||||
for(Iterator<String> iter = Arrays.stream(fragments).iterator(); iter.hasNext(); ) {
|
||||
String fragment = iter.next();
|
||||
Assert.assertTrue(LegacyURDecoder.isLegacyURFragment(fragment));
|
||||
legacyURDecoder.receivePart(fragment);
|
||||
Assert.assertEquals(iter.hasNext(), !legacyURDecoder.isComplete());
|
||||
}
|
||||
|
||||
UR ur = legacyURDecoder.decode();
|
||||
Assert.assertEquals(TestUtils.bytesToHex(ur.toBytes()), "7e4a61385e2550981b4b5633ab178eb077a30505fbd53f107ec1081e7cf0ca3c0dc0bfea5b8bfb5e6ffc91afd104c3aa756210b5dbc5118fd12c87ee04269815ba6a9968a0d0d3b7a9b631382a36bc70ab626d5670b4b48ff843f4d9a15631aa67c7aaf0ac6ce7e3bff03b2c9643e3375e47493c4e0f8635329d66fdec41b10ce74dcbf25fc15d829e7830c325643a98561f441b40a02e8353493e6afc16192fe99d90d8ca65539af77ddeaccc8943a37563a9ba83675bd5d4da7c60c9a172cf6940cbf0ec8fe04175a629932e3512c5d2aaea3cca3246f40a21ffdc33c3987dc7b880351230eb3759fe3c7dc7b2d3a20a95996ff0b7a0dba834f96beb64c14e3426fb051a936ba41569ab99c0066a6d9c0777a49e49e6cbad24d722a4c7da112432679264b9adc0a8cff9dd1fe0ee9ee2747f6a68537c389a7303a1af23c534ee6392bc17b04cf0fbce7689e66b673a440c04a9454005b0c76664639113458eb7d0902eff04d11138ce2a8ee16a9cd7c8926514efa9bd83ae7a4c139835f0fe0f68c628e0645c8524c30dfc314e825a7aa13224d98e2f7a9d12183a999bb1f28549c99a9072d99c05c24e0c84848c4fc147a094ab7b69e9cbea86952fccf15500fbb234ffe6ee6e6ded515c8016cb017ba36fb931ef276cec4ed22c1aed1495d2df3b3ce66c03f5b9ffa8434bf0e8fb149de94e050b3da178df1f76c00a366cb2801fabdf1a1e90cd3cd45ecb7a930a40b151455f76b726d552f31c21324992da257ff8bde2923dfd5d0d6b87233fae215ffacbecd96249099e7e3427d533db56cdb09c7475b4ce3314e33f43953a7370866cc11d85f00b71b15510b46c4b4fa490c660ddfeda0ceb1b8265995f7071c155ad1b57465fdc0fa81a73f9f19ac4872029d5844c1838f732e803043673e26cbc5b51297a324ff00a2d2d4222bad556b93d27c8e376e3ff8f9d37b3073410708ebb3d4dd7473d27212310b71a3c33a5c8f87f44824640e7f8970f4eda9364195c87a91172b8f085f1773641dde1ce21938746234055bc971ce2325f814e3eec60f781dd4faf52afd5be4a6b38656f7e9739f724cb7ccd4e4d01e802add3dc7b83191f894b3ee0ed752ee514d5ec55");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyDecodeShort() throws Exception {
|
||||
String[] fragments = new String[]{
|
||||
"ur:bytes/24ly5cfctcj4pxqmfdtr82ch36c80gc9qhasmxdxs3"
|
||||
};
|
||||
UR ur = LegacyURDecoder.decode(fragments);
|
||||
Assert.assertEquals(TestUtils.bytesToHex(ur.toBytes()), "7e4a61385e2550981b4b5633ab178eb077a30505fb");
|
||||
Assert.assertTrue(LegacyURDecoder.isLegacyURFragment(fragments[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeShortUR1() throws Exception {
|
||||
String[] fragments = new String[]{
|
||||
"UR:BYTES/1OF2/EFE6JTF4UM5NW43JVL95KX7NYQHX5V9HYGAAEQWN620XHSQ3ZNMS0PF2GR/TZAHQUMZWNLSZQZJQGQQQQQP4K6PXJYRYULEQDCUXER58CVPDHNSN80N39WME90TE5VMAWPJQRKQQQQQQQQ0LLLLLUQ7SQCQQQQQQQQQZCQPG4MKDDMGDJNQU53PZXVKD007R505KCSCZQQQQQQQQQGPRAVPKQQQQQQQQQQKQQ29MGDUNFESKL5AYZ03TTLECZT0DW7C".toLowerCase(),
|
||||
"UR:BYTES/2OF2/EFE6JTF4UM5NW43JVL95KX7NYQHX5V9HYGAAEQWN620XHSQ3ZNMS0PF2GR/N5NZYPSREN29X2CN2RSYEWHLJYZKHKCGH5U80D8UHRXHP2HD553EECGJ23A3SQQQQQQ9GQQQSQQQQQYQQQQQPQQQQQQQQPSQQQQQQQQ0YTU7N".toLowerCase()
|
||||
};
|
||||
UR ur = LegacyURDecoder.decode(fragments);
|
||||
Assert.assertEquals(TestUtils.bytesToHex(ur.toBytes()), "70736274ff0100520200000001adb4134883273f90371c364743e1816de7099df3895dbc95ebcd19beb83200ec0000000000ffffffff01e80300000000000016001457766b7686ca60e5221119966bdfe1d1f4b62181000000000001011f581b0000000000001600145da1bc9a730b7e9d209f15aff9c096f6bbd89d26220603ccd4532b1350e04cbaff91056bdb08bd3877b4fcb8cd70aaeda5239ce112547b180000000054000080000000800000008000000000060000000000");
|
||||
Arrays.stream(fragments).forEach(part -> Assert.assertTrue(LegacyURDecoder.isLegacyURFragment(part)));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue