-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix: doing address check if both public keys are available. (#12)
Co-authored-by: Mateusz Czeladka <mateusz.czeladka@cardanofoundation.org>
- Loading branch information
1 parent
23a6864
commit 0e92cff
Showing
8 changed files
with
517 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.cardanofoundation.cip30; | ||
|
||
import org.bouncycastle.crypto.digests.Blake2bDigest; | ||
|
||
public final class Hashing { | ||
|
||
public static byte[] blake2bHash224(byte[] in) { | ||
final Blake2bDigest hash = new Blake2bDigest(null, 28, null, null); | ||
hash.update(in, 0, in.length); | ||
final byte[] out = new byte[hash.getDigestSize()]; | ||
hash.doFinal(out, 0); | ||
|
||
return out; | ||
} | ||
|
||
} |
231 changes: 231 additions & 0 deletions
231
src/main/java/org/cardanofoundation/ext/bech32/Bech32.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
package org.cardanofoundation.ext.bech32; | ||
|
||
import org.bouncycastle.util.Arrays; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class Bech32 { | ||
|
||
private static final int TotalMaxLength = 108; //103 = mainnet length of a delegation address, 108 = testnet length | ||
private static final int CheckSumSize = 6; | ||
private static final int HrpMinLength = 1; | ||
private static final int HrpMaxLength = 83; | ||
private static final int HrpMinValue = 33; | ||
private static final int HrpMaxValue = 126; | ||
private static final char Separator = '1'; | ||
private static final String B32Chars = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; | ||
|
||
|
||
public static class Bech32Data { | ||
public final String hrp; | ||
public final byte[] data; | ||
public final byte ver; | ||
|
||
private Bech32Data(final String hrp, final byte[] data, byte ver) { | ||
this.hrp = hrp; | ||
this.data = data; | ||
this.ver = ver; | ||
} | ||
} | ||
|
||
public static boolean isValid(String bech32EncodedString) { | ||
if (!hasValidChars(bech32EncodedString)) { | ||
return false; | ||
} | ||
|
||
Tuple<String, byte[]> data = bech32Decode(bech32EncodedString); | ||
if (data._2.length < CheckSumSize) { | ||
return false; | ||
} | ||
|
||
return verifyChecksum(data._1, data._2); | ||
} | ||
|
||
public static boolean hasValidChars(String bech32EncodedString) { | ||
|
||
if ((bech32EncodedString == null || bech32EncodedString.isEmpty()) || bech32EncodedString.length() > TotalMaxLength) { | ||
return false; | ||
} | ||
|
||
// Reject mixed upper and lower characters. | ||
if (!bech32EncodedString.toLowerCase().equals(bech32EncodedString) && !bech32EncodedString.toUpperCase().equals(bech32EncodedString)) { | ||
return false; | ||
} | ||
|
||
// Check if it has a separator | ||
int sepIndex = bech32EncodedString.lastIndexOf(Separator); | ||
if (sepIndex == -1) { | ||
return false; | ||
} | ||
|
||
// Validate human readable part | ||
String hrp = bech32EncodedString.substring(0, sepIndex); | ||
if (!isValidHrp(hrp)) { | ||
return false; | ||
} | ||
|
||
// Validate data part | ||
String data = bech32EncodedString.substring(sepIndex + 1); | ||
if (data.length() < CheckSumSize || data.chars().anyMatch(x -> B32Chars.indexOf(x) == -1)) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static boolean isValidHrp(String hrp) { | ||
return hrp != null && | ||
hrp.trim().length() > 0 && | ||
hrp.length() >= HrpMinLength && | ||
hrp.length() < HrpMaxLength && | ||
hrp.chars().allMatch(character -> character >= HrpMinValue && character <= HrpMaxValue); | ||
} | ||
|
||
|
||
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; | ||
} | ||
|
||
private static byte[] expandHrp(String hrp) { | ||
byte[] result = new byte[(2 * hrp.length()) + 1]; | ||
for (int i = 0; i < hrp.length(); i++) { | ||
result[i] = (byte) (((int) hrp.charAt(i)) >> 5); | ||
result[i + hrp.length() + 1] = (byte) (((int) hrp.charAt(i)) & 0b0001_1111 /*=31*/); | ||
} | ||
return result; | ||
} | ||
|
||
private static boolean verifyChecksum(String hrp, byte[] data) { | ||
byte[] temp = Arrays.concatenate(expandHrp(hrp), data); | ||
return polymod(temp) == 1; | ||
} | ||
|
||
|
||
private static Tuple<String, byte[]> bech32Decode(String bech32EncodedString) { | ||
|
||
bech32EncodedString = bech32EncodedString.toLowerCase(); | ||
|
||
int separatorIndex = bech32EncodedString.lastIndexOf(Separator); | ||
String hrp = bech32EncodedString.substring(0, separatorIndex); | ||
String data = bech32EncodedString.substring(separatorIndex + 1); | ||
|
||
byte[] b32Arr = new byte[data.length()]; | ||
for (int i = 0; i < data.length(); i++) { | ||
b32Arr[i] = (byte) B32Chars.indexOf(data.charAt(i)); | ||
} | ||
|
||
return new Tuple(hrp, b32Arr); | ||
} | ||
|
||
private static byte[] convertBits(byte[] data, int fromBits, int toBits, boolean pad) { | ||
// TODO: Optimize Looping | ||
// We can use a method similar to BIP39 here to avoid the nested loop, usage of List, increase the speed, | ||
// and shorten this function to 3 lines. | ||
// Or convert to ulong[], loop through it (3 times) take 5 bits at a time or 8 bits at a time... | ||
int acc = 0; | ||
int bits = 0; | ||
int maxv = (1 << toBits) - 1; | ||
int maxacc = (1 << (fromBits + toBits - 1)) - 1; | ||
|
||
List<Byte> result = new ArrayList<>(); | ||
for (byte _b : data) { | ||
// Speed doesn't matter for this class but we can skip this check for 8 to 5 conversion. | ||
int b = Byte.toUnsignedInt(_b); | ||
if ((b >> fromBits) > 0) { | ||
System.out.println("a"); | ||
return null; | ||
} | ||
acc = ((acc << fromBits) | b) & maxacc; | ||
bits += fromBits; | ||
while (bits >= toBits) { | ||
bits -= toBits; | ||
result.add((byte) ((acc >> bits) & maxv)); | ||
} | ||
} | ||
if (pad) { | ||
if (bits > 0) { | ||
result.add((byte) ((acc << (toBits - bits)) & maxv)); | ||
} | ||
} else if (bits >= fromBits || (byte) ((acc << (toBits - bits)) & maxv) != 0) { | ||
System.out.println("b"); | ||
return null; | ||
} | ||
|
||
byte[] res = new byte[result.size()]; | ||
for (int i = 0; i < result.size(); i++) { | ||
res[i] = result.get(i); | ||
} | ||
|
||
return res; | ||
|
||
} | ||
|
||
public static Bech32Data decode(String bech32EncodedString) { | ||
Tuple<String, byte[]> bech32Data = bech32Decode(bech32EncodedString); | ||
|
||
String hrp = bech32Data._1; | ||
byte[] b32Arr = bech32Data._2; | ||
|
||
if (b32Arr.length < CheckSumSize) { | ||
throw new RuntimeException("Invalid data length."); | ||
} | ||
if (!verifyChecksum(hrp, b32Arr)) { | ||
throw new RuntimeException("Invalid checksum."); | ||
} | ||
|
||
|
||
byte[] data = Arrays.copyOfRange(b32Arr, 0, b32Arr.length - CheckSumSize); | ||
byte[] b256Arr = convertBits(data, 5, 8, false); | ||
if (b256Arr == null) { | ||
throw new RuntimeException("Invalid data format."); | ||
} | ||
|
||
byte witVer = b32Arr[0]; | ||
return new Bech32Data(hrp, b256Arr, witVer); | ||
} | ||
|
||
public static String encode(byte[] data, String hrp) { | ||
if (data == null || data.length == 0) | ||
throw new RuntimeException("Data can not be null or empty."); | ||
if (!isValidHrp(hrp)) | ||
throw new RuntimeException("Invalid HRP."); | ||
|
||
byte[] b32Arr = convertBits(data, 8, 5, true); | ||
byte[] checksum = calculateCheckSum(hrp, b32Arr); | ||
|
||
b32Arr = Arrays.concatenate(b32Arr, checksum); | ||
StringBuilder result = new StringBuilder(b32Arr.length + 1 + hrp.length()); | ||
result.append(hrp).append(Separator); | ||
for (byte b : b32Arr) { | ||
result.append(B32Chars.charAt(b)); | ||
} | ||
|
||
return result.toString(); | ||
} | ||
|
||
private static byte[] calculateCheckSum(String hrp, byte[] data) { | ||
// expand hrp, append data to it, and then add 6 zero bytes at the end. | ||
byte[] bytes = Arrays.concatenate(Arrays.concatenate(expandHrp(hrp), data), new byte[CheckSumSize]); | ||
|
||
// get polymod of the whole data and then flip the least significant bit. | ||
int pm = polymod(bytes) ^ 1; // | ||
|
||
byte[] result = new byte[6]; | ||
for (int i = 0; i < 6; i++) { | ||
result[i] = (byte) ((pm >> 5 * (5 - i)) & 0b0001_1111 /*=31*/); | ||
} | ||
return result; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package org.cardanofoundation.ext.bech32; | ||
|
||
public class Tuple<T, Z> { | ||
public T _1; | ||
public Z _2; | ||
|
||
public Tuple(T _1, Z _2) { | ||
this._1 = _1; | ||
this._2 = _2; | ||
} | ||
} |
Oops, something went wrong.