diff --git a/src/main/java/bisq/asset/coins/Nano.java b/src/main/java/bisq/asset/coins/Nano.java
new file mode 100644
index 0000000..802c250
--- /dev/null
+++ b/src/main/java/bisq/asset/coins/Nano.java
@@ -0,0 +1,1988 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AddressValidationResult;
+import bisq.asset.AddressValidator;
+import bisq.asset.Coin;
+
+import java.security.Key;
+import java.security.spec.AlgorithmParameterSpec;
+
+import java.nio.charset.Charset;
+
+import java.io.PrintStream;
+
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static bisq.asset.coins.Nano.Blake2b.Engine.Assert.assertFail;
+import static bisq.asset.coins.Nano.Blake2b.Engine.Assert.exclusiveLowerBound;
+import static bisq.asset.coins.Nano.Blake2b.Engine.Assert.inclusiveLowerBound;
+import static bisq.asset.coins.Nano.Blake2b.Engine.Assert.inclusiveUpperBound;
+import static bisq.asset.coins.Nano.Blake2b.Engine.LittleEndian.readInt;
+import static bisq.asset.coins.Nano.Blake2b.Engine.LittleEndian.readLong;
+import static bisq.asset.coins.Nano.Blake2b.Engine.LittleEndian.writeInt;
+import static bisq.asset.coins.Nano.Blake2b.Engine.LittleEndian.writeLong;
+
+/**
+ * Created by will on 6/8/18 at 11:52 PM.
+ *
+ * Code comes from
+ * https://github.com/frankh/nano/blob/1b510c2479605ad13aad4718c1ac875e3041fb2a/address/address.go#L29
+ * https://github.com/alphazero/Blake2b/blob/master/src/main/java/ove/crypto/digest/Blake2b.java
+ * https://gist.github.com/markov/5206312
+ *
+ * @author Will "n9Mtq4" Bresnahan
+ */
+public class Nano extends Coin {
+
+ public Nano() {
+ super("Nano", "NANO", new NanoAddressValidator());
+ }
+
+ public static class NanoAddressValidator implements AddressValidator {
+
+ private static final String ADDRESS_PREFIX_REGEX = "^(nano_|xrb_)";
+ private static final Pattern ADDRESS_PREFIX_PATTERN = Pattern.compile(ADDRESS_PREFIX_REGEX);
+ private static final String ADDRESS_FORMAT_REGEX = "[13456789abcdefghijkmnopqrstuwxyz]{60}";
+ private static final Pattern ADDRESS_FORMAT_PATTERN = Pattern.compile(ADDRESS_FORMAT_REGEX);
+
+ // based off of https://github.com/frankh/nano/blob/1b510c2479605ad13aad4718c1ac875e3041fb2a/address/address.go#L29
+ @Override
+ public AddressValidationResult validate(final String address) {
+
+ // check the prefix
+ final Matcher prefixMatcher = ADDRESS_PREFIX_PATTERN.matcher(address);
+ final boolean prefixValid = prefixMatcher.find();
+ if (!prefixValid)
+ return AddressValidationResult.invalidAddress("Invalid address prefix (must be nano_ or xrb_).");
+
+ // cut off prefix
+ final String addressDataChecksum = prefixMatcher.replaceFirst("");
+
+ // validate length and character set
+ final Matcher formatMatcher = ADDRESS_FORMAT_PATTERN.matcher(addressDataChecksum);
+ final boolean formatValid = formatMatcher.matches();
+ if (!formatValid)
+ return AddressValidationResult.invalidAddress("Invalid address format (length or character set).");
+
+ // data and checksum
+ // The nano address string is 260bits which doesn't fall on a
+ // byte boundary. pad with zeros to 280bits.
+ // (zeros are encoded as 1 in nano's 32bit alphabet)
+ try {
+ final String keyb32Nano = "1111" + addressDataChecksum.substring(0, 52);
+ final String checksum = addressDataChecksum.substring(52, addressDataChecksum.length());
+
+ final NanoBase32 base32 = new NanoBase32();
+
+ // derive public key
+ final byte[] rawKeyBytes = base32.decode(keyb32Nano);
+ // strip off upper 24 bits (3 bytes). 20 padding was added by us,
+ // 4 is unused as account is 256 bits.
+ final byte[] keyBytes = Arrays.copyOfRange(rawKeyBytes, 3, rawKeyBytes.length);
+
+
+ // get checksum from derived public key
+ final byte[] derivedChecksumBytes = Blake2b.Digest.newInstance(5).digest(keyBytes);
+
+ // reverse the bytes
+ for (int i = 0; i < derivedChecksumBytes.length / 2; i++) {
+ byte temp = derivedChecksumBytes[i];
+ derivedChecksumBytes[i] = derivedChecksumBytes[derivedChecksumBytes.length - i - 1];
+ derivedChecksumBytes[derivedChecksumBytes.length - i - 1] = temp;
+ }
+
+ // turn checksum bytes back into address encoding
+ base32.reset();
+ final String derivedChecksum = base32.encodeToString(derivedChecksumBytes);
+
+ // validate checksums, make sure they match
+ if (!checksum.equals(derivedChecksum))
+ return AddressValidationResult.invalidAddress("Address checksum failed.");
+
+ // all is good
+ return AddressValidationResult.validAddress();
+
+ } catch (Exception e) {
+ return AddressValidationResult.invalidAddress("Address is malformed, couldn't compute checksum.");
+ }
+
+ }
+
+ }
+
+ /**
+ * https://gist.github.com/markov/5206312
+ * modified to use nano's base 32 charset: 13456789abcdefghijkmnopqrstuwxyz
+ *
+ *
Provides Base32 encoding and decoding as defined by RFC 4648.
+ * However it uses a custom alphabet first coined by Douglas Crockford. Only addition to the alphabet is that 'u' and
+ * 'U' characters decode as if they were 'V' to improve mistakes by human input.
+ *
+ * This class operates directly on byte streams, and not character streams.
+ *
+ *
+ * @version $Id: Base32.java 1382498 2012-09-09 13:41:55Z sebb $
+ * @see RFC 4648
+ * @see Douglas Crockford's Base32 Encoding
+ * */
+ public static class NanoBase32 {
+
+ /**
+ * Mask used to extract 8 bits, used in decoding bytes
+ */
+ protected static final int MASK_8BITS = 0xff;
+ private static final Charset UTF8 = Charset.forName("UTF-8");
+ private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2;
+ /**
+ * Defines the default buffer size - currently {@value}
+ * - must be large enough for at least one encoded block+separator
+ */
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+ /**
+ * Mask used to extract 5 bits, used when encoding Base32 bytes
+ */
+ private static final int MASK_5BITS = 0x1f;
+ /**
+ * BASE32 characters are 5 bits in length.
+ * They are formed by taking a block of five octets to form a 40-bit string,
+ * which is converted into eight BASE32 characters.
+ */
+ private static final int BITS_PER_ENCODED_BYTE = 5;
+ private static final int BYTES_PER_ENCODED_BLOCK = 8;
+ private static final int BYTES_PER_UNENCODED_BLOCK = 5;
+ private static final byte PAD = '=';
+ /**
+ * This array is a lookup table that translates 5-bit positive integer index values into their "Base32 Alphabet"
+ * equivalents as specified in Table 3 of RFC 2045.
+ */
+ /*private static final byte[] ENCODE_TABLE = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M',
+ 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z'
+ };*/
+ private static final byte[] ENCODE_TABLE = {
+ '1', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c',
+ 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'w', 'x', 'y', 'z'
+ };
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * decodeSize = {@link #BYTES_PER_ENCODED_BLOCK} - 1 + lineSeparator.length;
+ */
+ private final int decodeSize;
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * encodeSize = {@link #BYTES_PER_ENCODED_BLOCK} + lineSeparator.length;
+ */
+ private final int encodeSize;
+ /**
+ * Wheather this encoder should use a padding character at the end of encoded Strings.
+ */
+ private final boolean usePaddingCharacter;
+ /**
+ * Buffer for streaming.
+ */
+ protected byte[] buffer;
+ /**
+ * Position where next character should be written in the buffer.
+ */
+ protected int pos;
+ /**
+ * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless,
+ * and must be thrown away.
+ */
+ protected boolean eof;
+ /**
+ * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding.
+ * This variable helps track that.
+ */
+ protected int modulus;
+ /**
+ * Place holder for the bytes we're dealing with for our based logic.
+ * Bitwise operations store and extract the encoding or decoding from this variable.
+ */
+ private long bitWorkArea;
+
+ public NanoBase32() {
+ this(false);
+ }
+
+ /**
+ * Creates a Base32 codec used for decoding and encoding.
+ *
+ * When encoding the line length is 0 (no chunking).
+ *
+ */
+ public NanoBase32(boolean usePaddingCharacter) {
+ this.usePaddingCharacter = usePaddingCharacter;
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK;
+ this.decodeSize = this.encodeSize - 1;
+ }
+
+ private static byte decode(byte octet) {
+
+ // just use an index of.
+ // could use a binary search, but not worth it for only 32 items
+ for (int i = 0; i < ENCODE_TABLE.length; i++) {
+ if (octet == ENCODE_TABLE[i]) return (byte) i;
+ }
+ return -1;
+
+ }
+
+ /**
+ * Checks if a byte value is whitespace or not.
+ * Whitespace is taken to mean: space, tab, CR, LF
+ *
+ * @param byteToCheck the byte to check
+ * @return true if byte is whitespace, false otherwise
+ */
+ protected static boolean isWhiteSpace(byte byteToCheck) {
+ switch (byteToCheck) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Tests a given String to see if it contains only valid characters within the alphabet.
+ * The method treats whitespace and PAD as valid.
+ *
+ * @param base32 String to test
+ * @return true
if all characters in the String are valid characters in the alphabet or if
+ * the String is empty; false
, otherwise
+ * @see #isInAlphabet(byte[], boolean)
+ */
+ public static boolean isInAlphabet(String base32) {
+ return isInAlphabet(base32.getBytes(UTF8), true);
+ }
+
+ /**
+ * Tests a given byte array to see if it contains only valid characters within the alphabet.
+ * The method optionally treats whitespace and pad as valid.
+ *
+ * @param arrayOctet byte array to test
+ * @param allowWSPad if true
, then whitespace and PAD are also allowed
+ * @return true
if all bytes are valid characters in the alphabet or if the byte array is empty;
+ * false
, otherwise
+ */
+ public static boolean isInAlphabet(byte[] arrayOctet, boolean allowWSPad) {
+ for (int i = 0; i < arrayOctet.length; i++) {
+ if (!isInAlphabet(arrayOctet[i]) &&
+ (!allowWSPad || (arrayOctet[i] != PAD) && !isWhiteSpace(arrayOctet[i]))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether or not the octet
is in the Base32 alphabet.
+ *
+ * @param octet The value to test
+ * @return true
if the value is defined in the the Base32 alphabet false
otherwise.
+ */
+ public static boolean isInAlphabet(byte octet) {
+ return decode(octet) != -1;
+ }
+
+ /**
+ * Returns the amount of buffered data available for reading.
+ *
+ * @return The amount of buffered data available for reading.
+ */
+ int available() { // package protected for access from I/O streams
+ return buffer != null ? pos : 0;
+ }
+
+ /**
+ * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}.
+ */
+ private void resizeBuffer() {
+ if (buffer == null) {
+ buffer = new byte[DEFAULT_BUFFER_SIZE];
+ pos = 0;
+ } else {
+ byte[] b = new byte[buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR];
+ System.arraycopy(buffer, 0, b, 0, buffer.length);
+ buffer = b;
+ }
+ }
+
+ /**
+ * Ensure that the buffer has room for size
bytes
+ *
+ * @param size minimum spare space required
+ */
+ protected void ensureBufferSize(int size) {
+ if ((buffer == null) || (buffer.length < pos + size)) {
+ resizeBuffer();
+ }
+ }
+
+ /**
+ * Extracts buffered data into the provided byte[] array, starting at position bPos,
+ * up to a maximum of bAvail bytes. Returns how many bytes were actually extracted.
+ *
+ * @param b byte[] array to extract the buffered data into.
+ * @return The number of bytes successfully extracted into the provided byte[] array.
+ */
+ int readResults(byte[] b) { // package protected for access from I/O streams
+ if (buffer != null) {
+ int len = available();
+ System.arraycopy(buffer, 0, b, 0, len);
+ buffer = null; // so hasData() will return false, and this method can return -1
+ return len;
+ }
+ return eof ? -1 : 0;
+ }
+
+ /**
+ * Resets this object to its initial newly constructed state.
+ */
+ private void reset() {
+ buffer = null;
+ pos = 0;
+ modulus = 0;
+ eof = false;
+ }
+
+ /**
+ * Encodes a String containing characters in the Base32 alphabet.
+ *
+ * @param pArray A String containing Base32 character data
+ * @return A String containing only Base32 character data
+ */
+ public String encodeToString(String pArray) {
+ return encodeToString(pArray.getBytes(UTF8));
+ }
+
+ /**
+ * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet.
+ *
+ * @param pArray a byte array containing binary data
+ * @return A String containing only Base32 character data
+ */
+ public String encodeToString(byte[] pArray) {
+ return new String(encode(pArray), UTF8);
+ }
+
+ /**
+ * Encodes a String containing characters in the Base32 alphabet.
+ *
+ * @param pArray A String containing Base32 character data
+ * @return A UTF-8 decoded String
+ */
+ public String decodeToString(String pArray) {
+ return decodeToString(pArray.getBytes(UTF8));
+ }
+
+ /**
+ * Decodes a byte[] containing binary data, into a String containing UTF-8 decoded String.
+ *
+ * @param pArray a byte array containing binary data
+ * @return A UTF-8 decoded String
+ */
+ public String decodeToString(byte[] pArray) {
+ return new String(decode(pArray), UTF8);
+ }
+
+ /**
+ * Decodes a String containing characters in the Base-N alphabet.
+ *
+ * @param pArray A String containing Base-N character data
+ * @return a byte array containing binary data
+ */
+ public byte[] decode(String pArray) {
+ return decode(pArray.getBytes(UTF8));
+ }
+
+ /**
+ * Encodes a String containing characters in the Base32 alphabet.
+ *
+ * @param pArray A String containing Base-N character data
+ * @return a byte array containing binary data
+ */
+ public byte[] encode(String pArray) {
+ return encode(pArray.getBytes(UTF8));
+ }
+
+ /**
+ * Decodes a byte[] containing characters in the Base-N alphabet.
+ *
+ * @param pArray A byte array containing Base-N character data
+ * @return a byte array containing binary data
+ */
+ public byte[] decode(byte[] pArray) {
+ reset();
+ if (pArray == null || pArray.length == 0) {
+ return pArray;
+ }
+ decode(pArray, 0, pArray.length);
+ decode(pArray, 0, -1); // Notify decoder of EOF.
+ byte[] result = new byte[pos];
+ readResults(result);
+ return result;
+ }
+
+ // The static final fields above are used for the original static byte[] methods on Base32.
+ // The private member fields below are used with the new streaming approach, which requires
+ // some state be preserved between calls of encode() and decode().
+
+ /**
+ * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet.
+ *
+ * @param pArray a byte array containing binary data
+ * @return A byte array containing only the basen alphabetic character data
+ */
+ public byte[] encode(byte[] pArray) {
+ reset();
+ if (pArray == null || pArray.length == 0) {
+ return pArray;
+ }
+ encode(pArray, 0, pArray.length);
+ encode(pArray, 0, -1); // Notify encoder of EOF.
+ byte[] buf = new byte[pos];
+ readResults(buf);
+ return buf;
+ }
+
+ /**
+ * Calculates the amount of space needed to encode the supplied array.
+ *
+ * @param pArray byte[] array which will later be encoded
+ * @return amount of space needed to encoded the supplied array.
+ * Returns a long since a max-len array will require > Integer.MAX_VALUE
+ */
+ public long getEncodedLength(byte[] pArray) {
+ // Calculate non-chunked size - rounded up to allow for padding
+ // cast to long is needed to avoid possibility of overflow
+ long len = ((pArray.length + BYTES_PER_UNENCODED_BLOCK - 1) / BYTES_PER_UNENCODED_BLOCK) * (long) BYTES_PER_ENCODED_BLOCK;
+ return len;
+ }
+
+ /**
+ *
+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once
+ * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1"
+ * call is not necessary when decoding, but it doesn't hurt, either.
+ *
+ *
+ * Ignores all non-Base32 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are
+ * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in,
+ * garbage-out philosophy: it will not check the provided data for validity.
+ *
+ *
+ * @param in byte[] array of ascii data to Base32 decode.
+ * @param inPos Position to start reading data from.
+ * @param inAvail Amount of bytes available from input for encoding.
+ *
+ * Output is written to {@link #buffer} as 8-bit octets, using {@link #pos} as the buffer position
+ */
+ void decode(byte[] in, int inPos, int inAvail) { // package protected for access from I/O streams
+ if (eof) {
+ return;
+ }
+ if (inAvail < 0) {
+ eof = true;
+ }
+ for (int i = 0; i < inAvail; i++) {
+ byte b = in[inPos++];
+ if (b == PAD) {
+ // We're done.
+ eof = true;
+ break;
+ } else {
+ ensureBufferSize(decodeSize);
+ if (isInAlphabet(b)) {
+ int result = decode(b);
+ modulus = (modulus + 1) % BYTES_PER_ENCODED_BLOCK;
+ bitWorkArea = (bitWorkArea << BITS_PER_ENCODED_BYTE) + result; // collect decoded bytes
+ if (modulus == 0) { // we can output the 5 bytes
+ buffer[pos++] = (byte) ((bitWorkArea >> 32) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 24) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) (bitWorkArea & MASK_8BITS);
+ }
+ }
+ }
+ }
+
+ // Two forms of EOF as far as Base32 decoder is concerned: actual
+ // EOF (-1) and first time '=' character is encountered in stream.
+ // This approach makes the '=' padding characters completely optional.
+ if (eof && modulus >= 2) { // if modulus < 2, nothing to do
+ ensureBufferSize(decodeSize);
+
+ // we ignore partial bytes, i.e. only multiples of 8 count
+ switch (modulus) {
+ case 2: // 10 bits, drop 2 and output one byte
+ buffer[pos++] = (byte) ((bitWorkArea >> 2) & MASK_8BITS);
+ break;
+ case 3: // 15 bits, drop 7 and output 1 byte
+ buffer[pos++] = (byte) ((bitWorkArea >> 7) & MASK_8BITS);
+ break;
+ case 4: // 20 bits = 2*8 + 4
+ bitWorkArea = bitWorkArea >> 4; // drop 4 bits
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
+ break;
+ case 5: // 25bits = 3*8 + 1
+ bitWorkArea = bitWorkArea >> 1;
+ buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
+ break;
+ case 6: // 30bits = 3*8 + 6
+ bitWorkArea = bitWorkArea >> 6;
+ buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
+ break;
+ case 7: // 35 = 4*8 +3
+ bitWorkArea = bitWorkArea >> 3;
+ buffer[pos++] = (byte) ((bitWorkArea >> 24) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
+ break;
+ }
+ }
+ }
+
+ /**
+ *
+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with
+ * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last
+ * remaining bytes (if not multiple of 5).
+ *
+ *
+ * @param in byte[] array of binary data to Base32 encode.
+ * @param inPos Position to start reading data from.
+ * @param inAvail Amount of bytes available from input for encoding.
+ */
+ void encode(byte[] in, int inPos, int inAvail) { // package protected for access from I/O streams
+ if (eof) {
+ return;
+ }
+ // inAvail < 0 is how we're informed of EOF in the underlying data we're
+ // encoding.
+ if (inAvail < 0) {
+ eof = true;
+ if (0 == modulus) {
+ return; // no leftovers to process
+ }
+ ensureBufferSize(encodeSize);
+ int savedPos = pos;
+ switch (modulus) { // % 5
+ case 1: // Only 1 octet; take top 5 bits then remainder
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 3) & MASK_5BITS]; // 8-1*5 = 3
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 2) & MASK_5BITS]; // 5-3=2
+ if (usePaddingCharacter) {
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ }
+ break;
+
+ case 2: // 2 octets = 16 bits to use
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 11) & MASK_5BITS]; // 16-1*5 = 11
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 6) & MASK_5BITS]; // 16-2*5 = 6
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 1) & MASK_5BITS]; // 16-3*5 = 1
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 4) & MASK_5BITS]; // 5-1 = 4
+ if (usePaddingCharacter) {
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ }
+ break;
+ case 3: // 3 octets = 24 bits to use
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 19) & MASK_5BITS]; // 24-1*5 = 19
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 14) & MASK_5BITS]; // 24-2*5 = 14
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 9) & MASK_5BITS]; // 24-3*5 = 9
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 4) & MASK_5BITS]; // 24-4*5 = 4
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 1) & MASK_5BITS]; // 5-4 = 1
+ if (usePaddingCharacter) {
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ }
+ break;
+ case 4: // 4 octets = 32 bits to use
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 27) & MASK_5BITS]; // 32-1*5 = 27
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 22) & MASK_5BITS]; // 32-2*5 = 22
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 17) & MASK_5BITS]; // 32-3*5 = 17
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 12) & MASK_5BITS]; // 32-4*5 = 12
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 7) & MASK_5BITS]; // 32-5*5 = 7
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 2) & MASK_5BITS]; // 32-6*5 = 2
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 3) & MASK_5BITS]; // 5-2 = 3
+ if (usePaddingCharacter) {
+ buffer[pos++] = PAD;
+ }
+ break;
+ }
+ } else {
+ for (int i = 0; i < inAvail; i++) {
+ ensureBufferSize(encodeSize);
+ modulus = (modulus + 1) % BYTES_PER_UNENCODED_BLOCK;
+ int b = in[inPos++];
+ if (b < 0) {
+ b += 256;
+ }
+ bitWorkArea = (bitWorkArea << 8) + b; // BITS_PER_BYTE
+ if (0 == modulus) { // we have enough bytes to create our output
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 35) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 30) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 25) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 20) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 15) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 10) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 5) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) bitWorkArea & MASK_5BITS];
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * https://github.com/alphazero/Blake2b/blob/master/src/main/java/ove/crypto/digest/Blake2b.java
+ *
+ * A Java implementation of BLAKE2B cryptographic digest algorithm.
+ *
+ * Joubin Mohammad Houshyar
+ * bushwick, nyc
+ * 02-14-2014
+ *
+ * --
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along with
+ * this software. If not, see .
+ * */
+ public interface Blake2b {
+ // ---------------------------------------------------------------------
+ // Specification
+ // ---------------------------------------------------------------------
+ public interface Spec {
+ /** pblock size of blake2b */
+ int param_bytes = 64;
+
+ /** pblock size of blake2b */
+ int block_bytes = 128;
+
+ /** maximum digest size */
+ int max_digest_bytes = 64;
+
+ /** maximum key sie */
+ int max_key_bytes = 64;
+
+ /** maximum salt size */
+ int max_salt_bytes = 16;
+
+ /** maximum personalization string size */
+ int max_personalization_bytes = 16;
+
+ /** length of h space vector array */
+ int state_space_len = 8;
+
+ /** max tree fanout value */
+ int max_tree_fantout = 0xFF;
+
+ /** max tree depth value */
+ int max_tree_depth = 0xFF;
+
+ /** max tree leaf length value.Note that this has uint32 semantics
+ and thus 0xFFFFFFFF is used as max value limit. */
+ int max_tree_leaf_length = 0xFFFFFFFF;
+
+ /** max node offset value. Note that this has uint64 semantics
+ and thus 0xFFFFFFFFFFFFFFFFL is used as max value limit. */
+ long max_node_offset = 0xFFFFFFFFFFFFFFFFL;
+
+ /** max tree inner length value */
+ int max_tree_inner_length = 0xFF;
+
+ /** initialization values map ref-Spec IV[i] -> slice iv[i*8:i*8+7] */
+ long[] IV = {
+ 0x6a09e667f3bcc908L,
+ 0xbb67ae8584caa73bL,
+ 0x3c6ef372fe94f82bL,
+ 0xa54ff53a5f1d36f1L,
+ 0x510e527fade682d1L,
+ 0x9b05688c2b3e6c1fL,
+ 0x1f83d9abfb41bd6bL,
+ 0x5be0cd19137e2179L
+ };
+
+ /** sigma per spec used in compress func generation - for reference only */
+ static byte[][] sigma = {
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+ {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
+ {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
+ {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
+ {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
+ {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
+ {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
+ {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
+ {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
+ {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0},
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+ {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}
+ };
+ }
+
+ // ---------------------------------------------------------------------
+ // API
+ // ---------------------------------------------------------------------
+ // TODO add ByteBuffer variants
+
+ /** */
+ void update(byte[] input);
+
+ /** */
+ void update(byte input);
+
+ /** */
+ void update(byte[] input, int offset, int len);
+
+ /** */
+ byte[] digest();
+
+ /** */
+ byte[] digest(byte[] input);
+
+ /** */
+ void digest(byte[] output, int offset, int len);
+
+ /** */
+ void reset();
+
+ // ---------------------------------------------------------------------
+ // Blake2b Message Digest
+ // ---------------------------------------------------------------------
+
+ /** Generalized Blake2b digest. */
+ public static class Digest extends Engine implements Blake2b {
+ private Digest(final Param p) {
+ super(p);
+ }
+
+ private Digest() {
+ super();
+ }
+
+ public static Digest newInstance() {
+ return new Digest();
+ }
+
+ public static Digest newInstance(final int digestLength) {
+ return new Digest(new Param().setDigestLength(digestLength));
+ }
+
+ public static Digest newInstance(Param p) {
+ return new Digest(p);
+ }
+ }
+
+ // ---------------------------------------------------------------------
+ // Blake2b Message Authentication Code
+ // ---------------------------------------------------------------------
+
+ /** Message Authentication Code (MAC) digest. */
+ public static class Mac extends Engine implements Blake2b {
+ private Mac(final Param p) {
+ super(p);
+ }
+
+ private Mac() {
+ super();
+ }
+
+ /** Blake2b.MAC 512 - using default Blake2b.Spec settings with given key */
+ public static Mac newInstance(final byte[] key) {
+ return new Mac(new Param().setKey(key));
+ }
+
+ /** Blake2b.MAC - using default Blake2b.Spec settings with given key, with given digest length */
+ public static Mac newInstance(final byte[] key, final int digestLength) {
+ return new Mac(new Param().setKey(key).setDigestLength(digestLength));
+ }
+
+ /** Blake2b.MAC - using default Blake2b.Spec settings with given java.security.Key, with given digest length */
+ public static Mac newInstance(final Key key, final int digestLength) {
+ return new Mac(new Param().setKey(key).setDigestLength(digestLength));
+ }
+
+ /** Blake2b.MAC - using the specified Parameters.
+ * @param p asserted valid configured Param with key */
+ public static Mac newInstance(Param p) {
+ assert p != null : "Param (p) is null";
+ assert p.hasKey() : "Param (p) not configured with a key";
+ return new Mac(p);
+ }
+ }
+
+ // ---------------------------------------------------------------------
+ // Blake2b Incremental Message Digest (Tree)
+ // ---------------------------------------------------------------------
+
+ /**
+ * Note that Tree is just a convenience class; incremental hash (tree)
+ * can be done directly with the Digest class.
+ *
+ * Further node, that tree does NOT accumulate the leaf hashes --
+ * you need to do that
+ */
+ public static class Tree {
+
+ final int depth;
+ final int fanout;
+ final int leaf_length;
+ final int inner_length;
+ final int digest_length;
+
+ /**
+ *
+ * @param fanout
+ * @param depth
+ * @param leaf_length size of data input for leaf nodes.
+ * @param inner_length note this is used also as digest-length for non-root nodes.
+ * @param digest_length final hash out digest-length for the tree
+ */
+ public Tree(
+ final int depth,
+ final int fanout,
+ final int leaf_length,
+ final int inner_length,
+ final int digest_length
+ ) {
+ this.fanout = fanout;
+ this.depth = depth;
+ this.leaf_length = leaf_length;
+ this.inner_length = inner_length;
+ this.digest_length = digest_length;
+ }
+
+ private Param treeParam() {
+ return new Param().
+ setDepth(depth).setFanout(fanout).setLeafLength(leaf_length).setInnerLength(inner_length);
+ }
+
+ /** returns the Digest for tree node @ (depth, offset) */
+ public final Digest getNode(final int depth, final int offset) {
+ final Param nodeParam = treeParam().setNodeDepth(depth).setNodeOffset(offset).setDigestLength(inner_length);
+ return Digest.newInstance(nodeParam);
+ }
+
+ /** returns the Digest for root node */
+ public final Digest getRoot() {
+ final int depth = this.depth - 1;
+ final Param rootParam = treeParam().setNodeDepth(depth).setNodeOffset(0L).setDigestLength(digest_length);
+ return Digest.newInstance(rootParam);
+ }
+ }
+
+ // ---------------------------------------------------------------------
+ // Engine
+ // ---------------------------------------------------------------------
+ static class Engine implements Blake2b {
+
+ /* G0 sigmas */
+ static final int[] sig_g00 = {0, 14, 11, 7, 9, 2, 12, 13, 6, 10, 0, 14,};
+ static final int[] sig_g01 = {1, 10, 8, 9, 0, 12, 5, 11, 15, 2, 1, 10,};
+
+ /* G1 sigmas */
+ static final int[] sig_g10 = {2, 4, 12, 3, 5, 6, 1, 7, 14, 8, 2, 4,};
+ static final int[] sig_g11 = {3, 8, 0, 1, 7, 10, 15, 14, 9, 4, 3, 8,};
+
+ /* G2 sigmas */
+ static final int[] sig_g20 = {4, 9, 5, 13, 2, 0, 14, 12, 11, 7, 4, 9,};
+ static final int[] sig_g21 = {5, 15, 2, 12, 4, 11, 13, 1, 3, 6, 5, 15,};
+
+ /* G3 sigmas */
+ static final int[] sig_g30 = {6, 13, 15, 11, 10, 8, 4, 3, 0, 1, 6, 13,};
+ static final int[] sig_g31 = {7, 6, 13, 14, 15, 3, 10, 9, 8, 5, 7, 6,};
+
+ /* G4 sigmas */
+ static final int[] sig_g40 = {8, 1, 10, 2, 14, 4, 0, 5, 12, 15, 8, 1,};
+ static final int[] sig_g41 = {9, 12, 14, 6, 1, 13, 7, 0, 2, 11, 9, 12,};
+
+ /* G5 sigmas */
+ static final int[] sig_g50 = {10, 0, 3, 5, 11, 7, 6, 15, 13, 9, 10, 0,};
+ static final int[] sig_g51 = {11, 2, 6, 10, 12, 5, 3, 4, 7, 14, 11, 2,};
+
+ /* G6 sigmas */
+ static final int[] sig_g60 = {12, 11, 7, 4, 6, 15, 9, 8, 1, 3, 12, 11,};
+ static final int[] sig_g61 = {13, 7, 1, 0, 8, 14, 2, 6, 4, 12, 13, 7,};
+
+ /* G7 sigmas */
+ static final int[] sig_g70 = {14, 5, 9, 15, 3, 1, 8, 2, 10, 13, 14, 5,};
+ static final int[] sig_g71 = {15, 3, 4, 8, 13, 9, 11, 10, 5, 0, 15, 3,};
+
+ // ---------------------------------------------------------------------
+ // Blake2b State(+) per reference implementation
+ // ---------------------------------------------------------------------
+ // REVU: address last_node TODO part of the Tree/incremental
+
+ /** per spec */
+ private final long[] h = new long[8];
+ /** per spec */
+ private final long[] t = new long[2];
+ /** per spec */
+ private final long[] f = new long[2];
+ /** per spec (tree) */
+ private boolean last_node = false;
+ /** pulled up 2b optimal */
+ private final long[] m = new long[16];
+ /** pulled up 2b optimal */
+ private final long[] v = new long[16];
+
+ /** compressor cache buffer */
+ private final byte[] buffer;
+ /** compressor cache buffer offset/cached data length */
+ private int buflen;
+
+ /** configuration params */
+ private final Param param;
+ /** digest length from init param - copied here on init */
+ private final int outlen;
+ /** to support update(byte) */
+ private byte[] oneByte;
+
+ /** read only */
+ private static byte[] zeropad = new byte[Spec.block_bytes];
+
+ /** a little bit of semantics */
+ interface flag {
+ int last_block = 0;
+ int last_node = 1;
+ }
+
+ // ---------------------------------------------------------------------
+ // Ctor & Initialization
+ // ---------------------------------------------------------------------
+
+ /** Basic use constructor pending (TODO) JCA/JCE compliance */
+ Engine() {
+ this(new Param());
+ }
+
+ /** User provided Param for custom configurations */
+ Engine(final Param param) {
+ assert param != null : "param is null";
+ this.param = param;
+ this.buffer = new byte[Spec.block_bytes];
+ this.oneByte = new byte[1];
+ this.outlen = param.getDigestLength();
+
+ if (param.getDepth() > Param.Default.depth) {
+ final int ndepth = param.getNodeDepth();
+ final long nxoff = param.getNodeOffset();
+ if (ndepth == param.getDepth() - 1) {
+ last_node = true;
+ assert param.getNodeOffset() == 0 : "root must have offset of zero";
+ } else if (param.getNodeOffset() == param.getFanout() - 1) {
+ this.last_node = true;
+ }
+ }
+
+ initialize();
+
+// Debug.dumpBuffer(System.out, "param bytes at init", param.getBytes());
+
+ }
+
+ private void initialize() {
+ // state vector h - copy values to address reset() requests
+ System.arraycopy(param.initialized_H(), 0, this.h, 0, Spec.state_space_len);
+
+// Debug.dumpArray("init H", this.h);
+ // if we have a key update initial block
+ // Note param has zero padded key_bytes to Spec.max_key_bytes
+ if (param.hasKey) {
+ this.update(param.key_bytes, 0, Spec.block_bytes);
+ }
+ }
+
+ public static void main(String... args) {
+ Blake2b mac = Blake2b.Mac.newInstance("LOVE".getBytes());
+ final byte[] hash = mac.digest("Salaam!".getBytes());
+// Debug.dumpBuffer(System.out, "-- mac hash --", hash);
+ }
+
+ // ---------------------------------------------------------------------
+ // interface: Blake2b API
+ // ---------------------------------------------------------------------
+
+ /** {@inheritDoc} */
+ @Override
+ final public void reset() {
+ // reset cache
+ this.buflen = 0;
+ for (int i = 0; i < buffer.length; i++) {
+ buffer[i] = (byte) 0;
+ }
+
+ // reset flags
+ this.f[0] = 0L;
+ this.f[1] = 0L;
+
+ // reset counters
+ this.t[0] = 0L;
+ this.t[1] = 0L;
+
+ // reset state vector
+ // NOTE: keep as last stmt as init calls update0 for MACs.
+ initialize();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ final public void update(final byte[] b, int off, int len) {
+ if (b == null) {
+ throw new IllegalArgumentException("input buffer (b) is null");
+ }
+ /* zero or more calls to compress */
+ // REVU: possibly the double buffering of c-ref is more sensible ..
+ // regardless, the hotspot is in the compress, as expected.
+ while (len > 0) {
+ if (buflen == 0) {
+ /* try compressing direct from input ? */
+ while (len > Spec.block_bytes) {
+ this.t[0] += Spec.block_bytes;
+ this.t[1] += this.t[0] == 0 ? 1 : 0;
+ compress(b, off);
+ len -= Spec.block_bytes;
+ off += Spec.block_bytes;
+ }
+ } else if (buflen == Spec.block_bytes) {
+ /* flush */
+ this.t[0] += Spec.block_bytes;
+ this.t[1] += this.t[0] == 0 ? 1 : 0;
+ compress(buffer, 0);
+ buflen = 0;
+ continue;
+ }
+
+ // "are we there yet?"
+ if (len == 0) return;
+
+ final int cap = Spec.block_bytes - buflen;
+ final int fill = len > cap ? cap : len;
+ System.arraycopy(b, off, buffer, buflen, fill);
+ buflen += fill;
+ len -= fill;
+ off += fill;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ final public void update(byte b) {
+ oneByte[0] = b;
+ update(oneByte, 0, 1);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ final public void update(byte[] input) {
+ update(input, 0, input.length);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ final public void digest(byte[] output, int off, int len) {
+ // zero pad last block; set last block flags; and compress
+ System.arraycopy(zeropad, 0, buffer, buflen, Spec.block_bytes - buflen);
+ if (buflen > 0) {
+ this.t[0] += buflen;
+ this.t[1] += this.t[0] == 0 ? 1 : 0;
+ }
+
+ this.f[flag.last_block] = 0xFFFFFFFFFFFFFFFFL;
+ this.f[flag.last_node] = this.last_node ? 0xFFFFFFFFFFFFFFFFL : 0x0L;
+
+ // compres and write final out (truncated to len) to output
+ compress(buffer, 0);
+ hashout(output, off, len);
+
+ reset();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ final public byte[] digest() throws IllegalArgumentException {
+ final byte[] out = new byte[outlen];
+ digest(out, 0, outlen);
+ return out;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ final public byte[] digest(byte[] input) {
+ update(input, 0, input.length);
+ return digest();
+ }
+
+ // ---------------------------------------------------------------------
+ // Internal Ops
+ // ---------------------------------------------------------------------
+
+ /**
+ * write out the digest output from the 'h' registers.
+ * truncate full output if necessary.
+ */
+ private void hashout(final byte[] out, final int offset, final int hashlen) {
+ // write max number of whole longs
+ final int lcnt = hashlen >>> 3;
+ long v = 0;
+ int i = offset;
+ for (int w = 0; w < lcnt; w++) {
+ v = h[w];
+ out[i++] = (byte) v;
+ v >>>= 8;
+ out[i++] = (byte) v;
+ v >>>= 8;
+ out[i++] = (byte) v;
+ v >>>= 8;
+ out[i++] = (byte) v;
+ v >>>= 8;
+ out[i++] = (byte) v;
+ v >>>= 8;
+ out[i++] = (byte) v;
+ v >>>= 8;
+ out[i++] = (byte) v;
+ v >>>= 8;
+ out[i++] = (byte) v;
+ }
+
+ // basta?
+ if (hashlen == Spec.max_digest_bytes) return;
+
+ // write the remaining bytes of a partial long value
+ v = h[lcnt];
+ i = lcnt << 3;
+ while (i < hashlen) {
+ out[offset + i] = (byte) v;
+ v >>>= 8;
+ ++i;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ /// Compression Kernel /////////////////////////////////////////// BEGIN
+ ////////////////////////////////////////////////////////////////////////
+
+ /** compress Spec.block_bytes data from b, from offset */
+ private void compress(final byte[] b, final int offset) {
+
+ // set m registers
+ // REVU: some small gains still possible here.
+ m[0] = ((long) b[offset] & 0xFF);
+ m[0] |= ((long) b[offset + 1] & 0xFF) << 8;
+ m[0] |= ((long) b[offset + 2] & 0xFF) << 16;
+ m[0] |= ((long) b[offset + 3] & 0xFF) << 24;
+ m[0] |= ((long) b[offset + 4] & 0xFF) << 32;
+ m[0] |= ((long) b[offset + 5] & 0xFF) << 40;
+ m[0] |= ((long) b[offset + 6] & 0xFF) << 48;
+ m[0] |= ((long) b[offset + 7]) << 56;
+
+ m[1] = ((long) b[offset + 8] & 0xFF);
+ m[1] |= ((long) b[offset + 9] & 0xFF) << 8;
+ m[1] |= ((long) b[offset + 10] & 0xFF) << 16;
+ m[1] |= ((long) b[offset + 11] & 0xFF) << 24;
+ m[1] |= ((long) b[offset + 12] & 0xFF) << 32;
+ m[1] |= ((long) b[offset + 13] & 0xFF) << 40;
+ m[1] |= ((long) b[offset + 14] & 0xFF) << 48;
+ m[1] |= ((long) b[offset + 15]) << 56;
+
+ m[2] = ((long) b[offset + 16] & 0xFF);
+ m[2] |= ((long) b[offset + 17] & 0xFF) << 8;
+ m[2] |= ((long) b[offset + 18] & 0xFF) << 16;
+ m[2] |= ((long) b[offset + 19] & 0xFF) << 24;
+ m[2] |= ((long) b[offset + 20] & 0xFF) << 32;
+ m[2] |= ((long) b[offset + 21] & 0xFF) << 40;
+ m[2] |= ((long) b[offset + 22] & 0xFF) << 48;
+ m[2] |= ((long) b[offset + 23]) << 56;
+
+ m[3] = ((long) b[offset + 24] & 0xFF);
+ m[3] |= ((long) b[offset + 25] & 0xFF) << 8;
+ m[3] |= ((long) b[offset + 26] & 0xFF) << 16;
+ m[3] |= ((long) b[offset + 27] & 0xFF) << 24;
+ m[3] |= ((long) b[offset + 28] & 0xFF) << 32;
+ m[3] |= ((long) b[offset + 29] & 0xFF) << 40;
+ m[3] |= ((long) b[offset + 30] & 0xFF) << 48;
+ m[3] |= ((long) b[offset + 31]) << 56;
+
+ m[4] = ((long) b[offset + 32] & 0xFF);
+ m[4] |= ((long) b[offset + 33] & 0xFF) << 8;
+ m[4] |= ((long) b[offset + 34] & 0xFF) << 16;
+ m[4] |= ((long) b[offset + 35] & 0xFF) << 24;
+ m[4] |= ((long) b[offset + 36] & 0xFF) << 32;
+ m[4] |= ((long) b[offset + 37] & 0xFF) << 40;
+ m[4] |= ((long) b[offset + 38] & 0xFF) << 48;
+ m[4] |= ((long) b[offset + 39]) << 56;
+
+ m[5] = ((long) b[offset + 40] & 0xFF);
+ m[5] |= ((long) b[offset + 41] & 0xFF) << 8;
+ m[5] |= ((long) b[offset + 42] & 0xFF) << 16;
+ m[5] |= ((long) b[offset + 43] & 0xFF) << 24;
+ m[5] |= ((long) b[offset + 44] & 0xFF) << 32;
+ m[5] |= ((long) b[offset + 45] & 0xFF) << 40;
+ m[5] |= ((long) b[offset + 46] & 0xFF) << 48;
+ m[5] |= ((long) b[offset + 47]) << 56;
+
+ m[6] = ((long) b[offset + 48] & 0xFF);
+ m[6] |= ((long) b[offset + 49] & 0xFF) << 8;
+ m[6] |= ((long) b[offset + 50] & 0xFF) << 16;
+ m[6] |= ((long) b[offset + 51] & 0xFF) << 24;
+ m[6] |= ((long) b[offset + 52] & 0xFF) << 32;
+ m[6] |= ((long) b[offset + 53] & 0xFF) << 40;
+ m[6] |= ((long) b[offset + 54] & 0xFF) << 48;
+ m[6] |= ((long) b[offset + 55]) << 56;
+
+ m[7] = ((long) b[offset + 56] & 0xFF);
+ m[7] |= ((long) b[offset + 57] & 0xFF) << 8;
+ m[7] |= ((long) b[offset + 58] & 0xFF) << 16;
+ m[7] |= ((long) b[offset + 59] & 0xFF) << 24;
+ m[7] |= ((long) b[offset + 60] & 0xFF) << 32;
+ m[7] |= ((long) b[offset + 61] & 0xFF) << 40;
+ m[7] |= ((long) b[offset + 62] & 0xFF) << 48;
+ m[7] |= ((long) b[offset + 63]) << 56;
+
+ m[8] = ((long) b[offset + 64] & 0xFF);
+ m[8] |= ((long) b[offset + 65] & 0xFF) << 8;
+ m[8] |= ((long) b[offset + 66] & 0xFF) << 16;
+ m[8] |= ((long) b[offset + 67] & 0xFF) << 24;
+ m[8] |= ((long) b[offset + 68] & 0xFF) << 32;
+ m[8] |= ((long) b[offset + 69] & 0xFF) << 40;
+ m[8] |= ((long) b[offset + 70] & 0xFF) << 48;
+ m[8] |= ((long) b[offset + 71]) << 56;
+
+ m[9] = ((long) b[offset + 72] & 0xFF);
+ m[9] |= ((long) b[offset + 73] & 0xFF) << 8;
+ m[9] |= ((long) b[offset + 74] & 0xFF) << 16;
+ m[9] |= ((long) b[offset + 75] & 0xFF) << 24;
+ m[9] |= ((long) b[offset + 76] & 0xFF) << 32;
+ m[9] |= ((long) b[offset + 77] & 0xFF) << 40;
+ m[9] |= ((long) b[offset + 78] & 0xFF) << 48;
+ m[9] |= ((long) b[offset + 79]) << 56;
+
+ m[10] = ((long) b[offset + 80] & 0xFF);
+ m[10] |= ((long) b[offset + 81] & 0xFF) << 8;
+ m[10] |= ((long) b[offset + 82] & 0xFF) << 16;
+ m[10] |= ((long) b[offset + 83] & 0xFF) << 24;
+ m[10] |= ((long) b[offset + 84] & 0xFF) << 32;
+ m[10] |= ((long) b[offset + 85] & 0xFF) << 40;
+ m[10] |= ((long) b[offset + 86] & 0xFF) << 48;
+ m[10] |= ((long) b[offset + 87]) << 56;
+
+ m[11] = ((long) b[offset + 88] & 0xFF);
+ m[11] |= ((long) b[offset + 89] & 0xFF) << 8;
+ m[11] |= ((long) b[offset + 90] & 0xFF) << 16;
+ m[11] |= ((long) b[offset + 91] & 0xFF) << 24;
+ m[11] |= ((long) b[offset + 92] & 0xFF) << 32;
+ m[11] |= ((long) b[offset + 93] & 0xFF) << 40;
+ m[11] |= ((long) b[offset + 94] & 0xFF) << 48;
+ m[11] |= ((long) b[offset + 95]) << 56;
+
+ m[12] = ((long) b[offset + 96] & 0xFF);
+ m[12] |= ((long) b[offset + 97] & 0xFF) << 8;
+ m[12] |= ((long) b[offset + 98] & 0xFF) << 16;
+ m[12] |= ((long) b[offset + 99] & 0xFF) << 24;
+ m[12] |= ((long) b[offset + 100] & 0xFF) << 32;
+ m[12] |= ((long) b[offset + 101] & 0xFF) << 40;
+ m[12] |= ((long) b[offset + 102] & 0xFF) << 48;
+ m[12] |= ((long) b[offset + 103]) << 56;
+
+ m[13] = ((long) b[offset + 104] & 0xFF);
+ m[13] |= ((long) b[offset + 105] & 0xFF) << 8;
+ m[13] |= ((long) b[offset + 106] & 0xFF) << 16;
+ m[13] |= ((long) b[offset + 107] & 0xFF) << 24;
+ m[13] |= ((long) b[offset + 108] & 0xFF) << 32;
+ m[13] |= ((long) b[offset + 109] & 0xFF) << 40;
+ m[13] |= ((long) b[offset + 110] & 0xFF) << 48;
+ m[13] |= ((long) b[offset + 111]) << 56;
+
+ m[14] = ((long) b[offset + 112] & 0xFF);
+ m[14] |= ((long) b[offset + 113] & 0xFF) << 8;
+ m[14] |= ((long) b[offset + 114] & 0xFF) << 16;
+ m[14] |= ((long) b[offset + 115] & 0xFF) << 24;
+ m[14] |= ((long) b[offset + 116] & 0xFF) << 32;
+ m[14] |= ((long) b[offset + 117] & 0xFF) << 40;
+ m[14] |= ((long) b[offset + 118] & 0xFF) << 48;
+ m[14] |= ((long) b[offset + 119]) << 56;
+
+ m[15] = ((long) b[offset + 120] & 0xFF);
+ m[15] |= ((long) b[offset + 121] & 0xFF) << 8;
+ m[15] |= ((long) b[offset + 122] & 0xFF) << 16;
+ m[15] |= ((long) b[offset + 123] & 0xFF) << 24;
+ m[15] |= ((long) b[offset + 124] & 0xFF) << 32;
+ m[15] |= ((long) b[offset + 125] & 0xFF) << 40;
+ m[15] |= ((long) b[offset + 126] & 0xFF) << 48;
+ m[15] |= ((long) b[offset + 127]) << 56;
+// Debug.dumpArray("m @ compress", m);
+//
+// Debug.dumpArray("h @ compress", h);
+// Debug.dumpArray("t @ compress", t);
+// Debug.dumpArray("f @ compress", f);
+
+ // set v registers
+ v[0] = h[0];
+ v[1] = h[1];
+ v[2] = h[2];
+ v[3] = h[3];
+ v[4] = h[4];
+ v[5] = h[5];
+ v[6] = h[6];
+ v[7] = h[7];
+ v[8] = 0x6a09e667f3bcc908L;
+ v[9] = 0xbb67ae8584caa73bL;
+ v[10] = 0x3c6ef372fe94f82bL;
+ v[11] = 0xa54ff53a5f1d36f1L;
+ v[12] = t[0] ^ 0x510e527fade682d1L;
+ v[13] = t[1] ^ 0x9b05688c2b3e6c1fL;
+ v[14] = f[0] ^ 0x1f83d9abfb41bd6bL;
+ v[15] = f[1] ^ 0x5be0cd19137e2179L;
+
+// Debug.dumpArray("v @ compress", v);
+ // the rounds
+ // REVU: let's try unrolling this again TODO do & bench
+ for (int r = 0; r < 12; r++) {
+
+ /** G (r, 0, 0, 4, 8, 12); */
+
+ v[0] = v[0] + v[4] + m[sig_g00[r]];
+ v[12] ^= v[0];
+ v[12] = (v[12] << 32) | (v[12] >>> 32);
+ v[8] = v[8] + v[12];
+ v[4] ^= v[8];
+ v[4] = (v[4] >>> 24) | (v[4] << 40);
+ v[0] = v[0] + v[4] + m[sig_g01[r]];
+ v[12] ^= v[0];
+ v[12] = (v[12] >>> 16) | (v[12] << 48);
+ v[8] = v[8] + v[12];
+ v[4] ^= v[8];
+ v[4] = (v[4] << 1) | (v[4] >>> 63);
+
+ /** G (r, 1, 1, 5, 9, 13); */
+
+ v[1] = v[1] + v[5] + m[sig_g10[r]];
+ v[13] ^= v[1];
+ v[13] = (v[13] << 32) | (v[13] >>> 32);
+ v[9] = v[9] + v[13];
+ v[5] ^= v[9];
+ v[5] = (v[5] >>> 24) | (v[5] << 40);
+ v[1] = v[1] + v[5] + m[sig_g11[r]];
+ v[13] ^= v[1];
+ v[13] = (v[13] >>> 16) | (v[13] << 48);
+ v[9] = v[9] + v[13];
+ v[5] ^= v[9];
+ v[5] = (v[5] << 1) | (v[5] >>> 63);
+
+ /** G (r, 2, 2, 6, 10, 14); */
+
+ v[2] = v[2] + v[6] + m[sig_g20[r]];
+ v[14] ^= v[2];
+ v[14] = (v[14] << 32) | (v[14] >>> 32);
+ v[10] = v[10] + v[14];
+ v[6] ^= v[10];
+ v[6] = (v[6] >>> 24) | (v[6] << 40);
+ v[2] = v[2] + v[6] + m[sig_g21[r]];
+ v[14] ^= v[2];
+ v[14] = (v[14] >>> 16) | (v[14] << 48);
+ v[10] = v[10] + v[14];
+ v[6] ^= v[10];
+ v[6] = (v[6] << 1) | (v[6] >>> 63);
+
+ /** G (r, 3, 3, 7, 11, 15); */
+
+ v[3] = v[3] + v[7] + m[sig_g30[r]];
+ v[15] ^= v[3];
+ v[15] = (v[15] << 32) | (v[15] >>> 32);
+ v[11] = v[11] + v[15];
+ v[7] ^= v[11];
+ v[7] = (v[7] >>> 24) | (v[7] << 40);
+ v[3] = v[3] + v[7] + m[sig_g31[r]];
+ v[15] ^= v[3];
+ v[15] = (v[15] >>> 16) | (v[15] << 48);
+ v[11] = v[11] + v[15];
+ v[7] ^= v[11];
+ v[7] = (v[7] << 1) | (v[7] >>> 63);
+
+ /** G (r, 4, 0, 5, 10, 15); */
+
+ v[0] = v[0] + v[5] + m[sig_g40[r]];
+ v[15] ^= v[0];
+ v[15] = (v[15] << 32) | (v[15] >>> 32);
+ v[10] = v[10] + v[15];
+ v[5] ^= v[10];
+ v[5] = (v[5] >>> 24) | (v[5] << 40);
+ v[0] = v[0] + v[5] + m[sig_g41[r]];
+ v[15] ^= v[0];
+ v[15] = (v[15] >>> 16) | (v[15] << 48);
+ v[10] = v[10] + v[15];
+ v[5] ^= v[10];
+ v[5] = (v[5] << 1) | (v[5] >>> 63);
+
+ /** G (r, 5, 1, 6, 11, 12); */
+
+ v[1] = v[1] + v[6] + m[sig_g50[r]];
+ v[12] ^= v[1];
+ v[12] = (v[12] << 32) | (v[12] >>> 32);
+ v[11] = v[11] + v[12];
+ v[6] ^= v[11];
+ v[6] = (v[6] >>> 24) | (v[6] << 40);
+ v[1] = v[1] + v[6] + +m[sig_g51[r]];
+ v[12] ^= v[1];
+ v[12] = (v[12] >>> 16) | (v[12] << 48);
+ v[11] = v[11] + v[12];
+ v[6] ^= v[11];
+ v[6] = (v[6] << 1) | (v[6] >>> 63);
+
+ /** G (r, 6, 2, 7, 8, 13); */
+
+ v[2] = v[2] + v[7] + m[sig_g60[r]];
+ v[13] ^= v[2];
+ v[13] = (v[13] << 32) | (v[13] >>> 32);
+ v[8] = v[8] + v[13];
+ v[7] ^= v[8];
+ v[7] = (v[7] >>> 24) | (v[7] << 40);
+ v[2] = v[2] + v[7] + m[sig_g61[r]];
+ v[13] ^= v[2];
+ v[13] = (v[13] >>> 16) | (v[13] << 48);
+ v[8] = v[8] + v[13];
+ v[7] ^= v[8];
+ v[7] = (v[7] << 1) | (v[7] >>> 63);
+
+ /** G (r, 7, 3, 4, 9, 14); */
+
+ v[3] = v[3] + v[4] + m[sig_g70[r]];
+ v[14] ^= v[3];
+ v[14] = (v[14] << 32) | (v[14] >>> 32);
+ v[9] = v[9] + v[14];
+ v[4] ^= v[9];
+ v[4] = (v[4] >>> 24) | (v[4] << 40);
+ v[3] = v[3] + v[4] + m[sig_g71[r]];
+ v[14] ^= v[3];
+ v[14] = (v[14] >>> 16) | (v[14] << 48);
+ v[9] = v[9] + v[14];
+ v[4] ^= v[9];
+ v[4] = (v[4] << 1) | (v[4] >>> 63);
+ }
+
+ // Update state vector h
+ h[0] ^= v[0] ^ v[8];
+ h[1] ^= v[1] ^ v[9];
+ h[2] ^= v[2] ^ v[10];
+ h[3] ^= v[3] ^ v[11];
+ h[4] ^= v[4] ^ v[12];
+ h[5] ^= v[5] ^ v[13];
+ h[6] ^= v[6] ^ v[14];
+ h[7] ^= v[7] ^ v[15];
+
+// Debug.dumpArray("v @ compress end", v);
+// Debug.dumpArray("h @ compress end", h);
+ /* kaamil */
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ /// Compression Kernel //////////////////////////////////////////// FINI
+ ////////////////////////////////////////////////////////////////////////
+
+ /* TEMP - remove at will */
+ public static class Debug {
+ public static void dumpState(Blake2b.Engine e, final String mark) {
+ System.out.format("-- MARK == @ %s @ ===========\n", mark);
+ dumpArray("register t", e.t);
+ dumpArray("register h", e.h);
+ dumpArray("register f", e.f);
+ dumpArray("register offset", new long[]{e.buflen});
+ System.out.format("-- END MARK =================\n");
+ }
+
+ public static void dumpArray(final String label, final long[] b) {
+ System.out.format("-- %s -- :\n{\n", label);
+ for (int j = 0; j < b.length; ++j) {
+ System.out.format(" [%2d] : %016X\n", j, b[j]);
+ }
+ System.out.format("}\n");
+ }
+
+ public static void dumpBuffer(final PrintStream out, final String label, final byte[] b) {
+ dumpBuffer(out, label, b, 0, b.length);
+ }
+
+ public static void dumpBuffer(final PrintStream out, final byte[] b) {
+ dumpBuffer(out, null, b, 0, b.length);
+ }
+
+ public static void dumpBuffer(final PrintStream out, final byte[] b, final int offset, final int len) {
+ dumpBuffer(out, null, b, offset, len);
+ }
+
+ public static void dumpBuffer(final PrintStream out, final String label, final byte[] b, final int offset, final int len) {
+ if (label != null)
+ out.format("-- %s -- :\n", label);
+ out.format("{\n ", label);
+ for (int j = 0; j < len; ++j) {
+ out.format("%02X", b[j + offset]);
+ if (j + 1 < len) {
+ if ((j + 1) % 8 == 0) out.print("\n ");
+ else out.print(' ');
+ }
+ }
+ out.format("\n}\n");
+ }
+ }
+ /* TEMP - remove at will */
+
+ // ---------------------------------------------------------------------
+ // Helper for assert error messages
+ // ---------------------------------------------------------------------
+ public static final class Assert {
+ public final static String exclusiveUpperBound = "'%s' %d is >= %d";
+ public final static String inclusiveUpperBound = "'%s' %d is > %d";
+ public final static String exclusiveLowerBound = "'%s' %d is <= %d";
+ public final static String inclusiveLowerBound = "'%s' %d is < %d";
+
+ static String assertFail(final String name, final T v, final String err, final T spec) {
+ new Exception().printStackTrace();
+ return String.format(err, name, v, spec);
+ }
+ }
+ // ---------------------------------------------------------------------
+ // Little Endian Codecs (inlined in the compressor)
+ /*
+ * impl note: these are not library funcs and used in hot loops, so no
+ * null or bounds checks are performed. For our purposes, this is OK.
+ */
+ // ---------------------------------------------------------------------
+
+ public static class LittleEndian {
+ private static final byte[] hex_digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+ private static final byte[] HEX_digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ /** @return hex rep of byte (lower case). */
+ static public String toHexStr(final byte[] b) {
+ return toHexStr(b, false); // because String class is slower.
+ }
+
+ static public String toHexStr(final byte[] b, boolean upperCase) {
+ final int len = b.length;
+ final byte[] digits = new byte[len * 2];
+ final byte[] hex_rep = upperCase ? HEX_digits : hex_digits;
+ for (int i = 0; i < len; i++) {
+ digits[i * 2] = hex_rep[(byte) (b[i] >> 4 & 0x0F)];
+ digits[i * 2 + 1] = hex_rep[(byte) (b[i] & 0x0F)];
+ }
+ return new String(digits);
+ }
+
+ public static int readInt(final byte[] b, int off) {
+ int v0
+ = ((int) b[off++] & 0xFF);
+ v0 |= ((int) b[off++] & 0xFF) << 8;
+ v0 |= ((int) b[off++] & 0xFF) << 16;
+ v0 |= ((int) b[off]) << 24;
+ return v0;
+ }
+
+ /** Little endian - byte[] to long */
+ public static long readLong(final byte[] b, int off) {
+ long v0
+ = ((long) b[off++] & 0xFF);
+ v0 |= ((long) b[off++] & 0xFF) << 8;
+ v0 |= ((long) b[off++] & 0xFF) << 16;
+ v0 |= ((long) b[off++] & 0xFF) << 24;
+ v0 |= ((long) b[off++] & 0xFF) << 32;
+ v0 |= ((long) b[off++] & 0xFF) << 40;
+ v0 |= ((long) b[off++] & 0xFF) << 48;
+ v0 |= ((long) b[off]) << 56;
+ return v0;
+ }
+ /** */
+ /** Little endian - long to byte[] */
+ public static void writeLong(long v, final byte[] b, final int off) {
+ b[off] = (byte) v;
+ v >>>= 8;
+ b[off + 1] = (byte) v;
+ v >>>= 8;
+ b[off + 2] = (byte) v;
+ v >>>= 8;
+ b[off + 3] = (byte) v;
+ v >>>= 8;
+ b[off + 4] = (byte) v;
+ v >>>= 8;
+ b[off + 5] = (byte) v;
+ v >>>= 8;
+ b[off + 6] = (byte) v;
+ v >>>= 8;
+ b[off + 7] = (byte) v;
+ }
+
+ /** Little endian - int to byte[] */
+ public static void writeInt(int v, final byte[] b, final int off) {
+ b[off] = (byte) v;
+ v >>>= 8;
+ b[off + 1] = (byte) v;
+ v >>>= 8;
+ b[off + 2] = (byte) v;
+ v >>>= 8;
+ b[off + 3] = (byte) v;
+ }
+ }
+ }
+ // ---------------------------------------------------------------------
+ // digest parameter (block)
+ // ---------------------------------------------------------------------
+
+ /** Blake2b configuration parameters block per spec */
+ // REVU: need to review a revert back to non-lazy impl TODO: do & bench
+ public static class Param implements AlgorithmParameterSpec {
+ interface Xoff {
+ int digest_length = 0;
+ int key_length = 1;
+ int fanout = 2;
+ int depth = 3;
+ int leaf_length = 4;
+ int node_offset = 8;
+ int node_depth = 16;
+ int inner_length = 17;
+ int reserved = 18;
+ int salt = 32;
+ int personal = 48;
+ }
+
+ public interface Default {
+ byte digest_length = Spec.max_digest_bytes;
+ byte key_length = 0;
+ byte fanout = 1;
+ byte depth = 1;
+ int leaf_length = 0;
+ long node_offset = 0;
+ byte node_depth = 0;
+ byte inner_length = 0;
+ }
+
+ /** default bytes of Blake2b parameter block */
+ final static byte[] default_bytes = new byte[Spec.param_bytes];
+
+ /** initialize default_bytes */
+ static {
+ default_bytes[Xoff.digest_length] = Default.digest_length;
+ default_bytes[Xoff.key_length] = Default.key_length;
+ default_bytes[Xoff.fanout] = Default.fanout;
+ default_bytes[Xoff.depth] = Default.depth;
+ /* def. leaf_length is 0 fill and already set by new byte[] */
+ /* def. node_offset is 0 fill and already set by new byte[] */
+ default_bytes[Xoff.node_depth] = Default.node_depth;
+ default_bytes[Xoff.inner_length] = Default.inner_length;
+ /* def. salt is 0 fill and already set by new byte[] */
+ /* def. personal is 0 fill and already set by new byte[] */
+ }
+
+ /** default Blake2b h vector */
+ final static long[] default_h = new long[Spec.state_space_len];
+
+ static {
+ default_h[0] = readLong(default_bytes, 0);
+ default_h[1] = readLong(default_bytes, 8);
+ default_h[2] = readLong(default_bytes, 16);
+ default_h[3] = readLong(default_bytes, 24);
+ default_h[4] = readLong(default_bytes, 32);
+ default_h[5] = readLong(default_bytes, 40);
+ default_h[6] = readLong(default_bytes, 48);
+ default_h[7] = readLong(default_bytes, 56);
+
+ default_h[0] ^= Spec.IV[0];
+ default_h[1] ^= Spec.IV[1];
+ default_h[2] ^= Spec.IV[2];
+ default_h[3] ^= Spec.IV[3];
+ default_h[4] ^= Spec.IV[4];
+ default_h[5] ^= Spec.IV[5];
+ default_h[6] ^= Spec.IV[6];
+ default_h[7] ^= Spec.IV[7];
+ }
+
+ /** */
+ private boolean hasKey = false;
+ /** not sure how to make this secure - TODO */
+ private byte[] key_bytes = null;
+ /** */
+ private byte[] bytes = null;
+ /** */
+ private final long[] h = new long[Spec.state_space_len];
+
+ /** */
+ public Param() {
+ System.arraycopy(default_h, 0, h, 0, Spec.state_space_len);
+ }
+
+ /** */
+ public long[] initialized_H() {
+ return h;
+ }
+
+ /** package only - copy returned - do not use in functional loops */
+ public byte[] getBytes() {
+ lazyInitBytes();
+ byte[] copy = new byte[bytes.length];
+ System.arraycopy(bytes, 0, copy, 0, bytes.length);
+ return copy;
+ }
+
+ final byte getByteParam(final int xoffset) {
+ byte[] _bytes = bytes;
+ if (_bytes == null) _bytes = Param.default_bytes;
+ return _bytes[xoffset];
+ }
+
+ final int getIntParam(final int xoffset) {
+ byte[] _bytes = bytes;
+ if (_bytes == null) _bytes = Param.default_bytes;
+ return readInt(_bytes, xoffset);
+ }
+
+ final long getLongParam(final int xoffset) {
+ byte[] _bytes = bytes;
+ if (_bytes == null) _bytes = Param.default_bytes;
+ return readLong(_bytes, xoffset);
+ }
+
+ // TODO same for tree params depth, fanout, inner, node-depth, node-offset
+ public final int getDigestLength() {
+ return (int) getByteParam(Xoff.digest_length);
+ }
+
+ public final int getKeyLength() {
+ return (int) getByteParam(Xoff.key_length);
+ }
+
+ public final int getFanout() {
+ return (int) getByteParam(Xoff.fanout);
+ }
+
+ public final int getDepth() {
+ return (int) getByteParam(Xoff.depth);
+ }
+
+ public final int getLeafLength() {
+ return getIntParam(Xoff.leaf_length);
+ }
+
+ public final long getNodeOffset() {
+ return getLongParam(Xoff.node_offset);
+ }
+
+ public final int getNodeDepth() {
+ return (int) getByteParam(Xoff.node_depth);
+ }
+
+ public final int getInnerLength() {
+ return (int) getByteParam(Xoff.inner_length);
+ }
+
+ public final boolean hasKey() {
+ return this.hasKey;
+ }
+
+ @Override
+ public Param clone() {
+ final Param clone = new Param();
+ System.arraycopy(this.h, 0, clone.h, 0, h.length);
+ clone.lazyInitBytes();
+ System.arraycopy(this.bytes, 0, clone.bytes, 0, this.bytes.length);
+
+ if (this.hasKey) {
+ clone.hasKey = this.hasKey;
+ clone.key_bytes = new byte[Spec.max_key_bytes * 2];
+ System.arraycopy(this.key_bytes, 0, clone.key_bytes, 0, this.key_bytes.length);
+ }
+ return clone;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ /// lazy setters - write directly to the bytes image of param block ////
+ ////////////////////////////////////////////////////////////////////////
+ final void lazyInitBytes() {
+ if (bytes == null) {
+ bytes = new byte[Spec.param_bytes];
+ System.arraycopy(Param.default_bytes, 0, bytes, 0, Spec.param_bytes);
+ }
+ }
+
+ /* 0-7 inclusive */
+ public final Param setDigestLength(int len) {
+ assert len > 0 : assertFail("len", len, exclusiveLowerBound, 0);
+ assert len <= Spec.max_digest_bytes : assertFail("len", len, inclusiveUpperBound, Spec.max_digest_bytes);
+
+ lazyInitBytes();
+ bytes[Xoff.digest_length] = (byte) len;
+ h[0] = readLong(bytes, 0);
+ h[0] ^= Spec.IV[0];
+ return this;
+ }
+
+ public final Param setKey(final Key key) {
+ assert key != null : "key is null";
+ final byte[] keybytes = key.getEncoded();
+ assert keybytes != null : "key.encoded() is null";
+
+ return this.setKey(keybytes);
+ }
+
+ public final Param setKey(final byte[] key) {
+ assert key != null : "key is null";
+ assert key.length >= 0 : assertFail("key.length", key.length, inclusiveUpperBound, 0);
+ assert key.length <= Spec.max_key_bytes : assertFail("key.length", key.length, inclusiveUpperBound, Spec.max_key_bytes);
+
+ // zeropad keybytes
+ this.key_bytes = new byte[Spec.max_key_bytes * 2];
+ System.arraycopy(key, 0, this.key_bytes, 0, key.length);
+ lazyInitBytes();
+ bytes[Xoff.key_length] = (byte) key.length; // checked c ref; this is correct
+ h[0] = readLong(bytes, 0);
+ h[0] ^= Spec.IV[0];
+ this.hasKey = true;
+ return this;
+ }
+
+ public final Param setFanout(int fanout) {
+ assert fanout > 0 : assertFail("fanout", fanout, exclusiveLowerBound, 0);
+
+ lazyInitBytes();
+ bytes[Xoff.fanout] = (byte) fanout;
+ h[0] = readLong(bytes, 0);
+ h[0] ^= Spec.IV[0];
+ return this;
+ }
+
+ public final Param setDepth(int depth) {
+ assert depth > 0 : assertFail("depth", depth, exclusiveLowerBound, 0);
+
+ lazyInitBytes();
+ bytes[Xoff.depth] = (byte) depth;
+ h[0] = readLong(bytes, 0);
+ h[0] ^= Spec.IV[0];
+ return this;
+ }
+
+ public final Param setLeafLength(int leaf_length) {
+ assert leaf_length >= 0 : assertFail("leaf_length", leaf_length, inclusiveLowerBound, 0);
+
+ lazyInitBytes();
+ writeInt(leaf_length, bytes, Xoff.leaf_length);
+ h[0] = readLong(bytes, 0);
+ h[0] ^= Spec.IV[0];
+ return this;
+ }
+
+ /* 8-15 inclusive */
+ public final Param setNodeOffset(long node_offset) {
+ assert node_offset >= 0 : assertFail("node_offset", node_offset, inclusiveLowerBound, 0);
+
+ lazyInitBytes();
+ writeLong(node_offset, bytes, Xoff.node_offset);
+ h[1] = readLong(bytes, Xoff.node_offset);
+ h[1] ^= Spec.IV[1];
+ return this;
+ }
+
+ /* 16-23 inclusive */
+ public final Param setNodeDepth(int node_depth) {
+ assert node_depth >= 0 : assertFail("node_depth", node_depth, inclusiveLowerBound, 0);
+
+ lazyInitBytes();
+ bytes[Xoff.node_depth] = (byte) node_depth;
+ h[2] = readLong(bytes, Xoff.node_depth);
+ h[2] ^= Spec.IV[2];
+ h[3] = readLong(bytes, Xoff.node_depth + 8);
+ h[3] ^= Spec.IV[3];
+ return this;
+ }
+
+ public final Param setInnerLength(int inner_length) {
+ assert inner_length >= 0 : assertFail("inner_length", inner_length, inclusiveLowerBound, 0);
+
+ lazyInitBytes();
+ bytes[Xoff.inner_length] = (byte) inner_length;
+ h[2] = readLong(bytes, Xoff.node_depth);
+ h[2] ^= Spec.IV[2];
+ h[3] = readLong(bytes, Xoff.node_depth + 8);
+ h[3] ^= Spec.IV[3];
+ return this;
+ }
+
+ /* 24-31 masked by reserved and remain unchanged */
+
+ /* 32-47 inclusive */
+ public final Param setSalt(final byte[] salt) {
+ assert salt != null : "salt is null";
+ assert salt.length <= Spec.max_salt_bytes : assertFail("salt.length", salt.length, inclusiveUpperBound, Spec.max_salt_bytes);
+
+ lazyInitBytes();
+ Arrays.fill(bytes, Xoff.salt, Xoff.salt + Spec.max_salt_bytes, (byte) 0);
+ System.arraycopy(salt, 0, bytes, Xoff.salt, salt.length);
+ h[4] = readLong(bytes, Xoff.salt);
+ h[4] ^= Spec.IV[4];
+ h[5] = readLong(bytes, Xoff.salt + 8);
+ h[5] ^= Spec.IV[5];
+ return this;
+ }
+
+ /* 48-63 inclusive */
+ public final Param setPersonal(byte[] personal) {
+ assert personal != null : "personal is null";
+ assert personal.length <= Spec.max_personalization_bytes : assertFail("personal.length", personal.length, inclusiveUpperBound, Spec.max_personalization_bytes);
+
+ lazyInitBytes();
+ Arrays.fill(bytes, Xoff.personal, Xoff.personal + Spec.max_personalization_bytes, (byte) 0);
+ System.arraycopy(personal, 0, bytes, Xoff.personal, personal.length);
+ h[6] = readLong(bytes, Xoff.personal);
+ h[6] ^= Spec.IV[6];
+ h[7] = readLong(bytes, Xoff.personal + 8);
+ h[7] ^= Spec.IV[7];
+ return this;
+ }
+ ////////////////////////////////////////////////////////////////////////
+ /// lazy setters /////////////////////////////////////////////////// END
+ ////////////////////////////////////////////////////////////////////////
+ }
+ }
+
+}
diff --git a/src/main/resources/META-INF/services/bisq.asset.Asset b/src/main/resources/META-INF/services/bisq.asset.Asset
index 445a4f4..4dc22f8 100644
--- a/src/main/resources/META-INF/services/bisq.asset.Asset
+++ b/src/main/resources/META-INF/services/bisq.asset.Asset
@@ -64,6 +64,7 @@ bisq.asset.coins.MFCoin
bisq.asset.coins.MicroCoin
bisq.asset.coins.Monero
bisq.asset.coins.Namecoin
+bisq.asset.coins.Nano
bisq.asset.coins.NavCoin
bisq.asset.coins.NEETCOIN
bisq.asset.coins.Nilu
diff --git a/src/test/java/bisq/asset/coins/NanoTest.java b/src/test/java/bisq/asset/coins/NanoTest.java
new file mode 100644
index 0000000..5b113dc
--- /dev/null
+++ b/src/test/java/bisq/asset/coins/NanoTest.java
@@ -0,0 +1,55 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.asset.coins;
+
+import bisq.asset.AbstractAssetTest;
+
+/**
+ * Created by will on 6/9/18 at 12:03 AM.
+ *
+ * @author Will "n9Mtq4" Bresnahan
+ */
+public class NanoTest extends AbstractAssetTest {
+
+ public NanoTest() {
+ super(new Nano());
+ }
+
+ @Override
+ public void testValidAddresses() {
+ assertValidAddress("xrb_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3");
+ assertValidAddress("xrb_35jjmmmh81kydepzeuf9oec8hzkay7msr6yxagzxpcht7thwa5bus5tomgz9");
+ assertValidAddress("xrb_1111111111111111111111111111111111111111111111111111hifc8npp");
+ assertValidAddress("nano_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3");
+ assertValidAddress("nano_35jjmmmh81kydepzeuf9oec8hzkay7msr6yxagzxpcht7thwa5bus5tomgz9");
+ assertValidAddress("nano_1111111111111111111111111111111111111111111111111111hifc8npp");
+ }
+
+ @Override
+ public void testInvalidAddresses() {
+ assertInvalidAddress("abc_1111111111111111111111111111111111111111111111111111hifc8npp"); // invalid prefix
+ assertInvalidAddress("xrb_1111111111111111111111111111111111111111111111111111hifc8npx"); // invalid checksum
+ assertInvalidAddress("xrb_1311111111111111111111111111111111111111111111111111hifc8npp"); // invalid checksum
+ assertInvalidAddress("xrb_1211111111111111111111111111111111111111111111111111hifc8npp"); // invalid character
+ assertInvalidAddress("XRB_1111111111111111111111111111111111111111111111111111HIFC8NPX"); // not lowercase
+ assertInvalidAddress("xrb_11111111111111111111111111111111111111111111111hifc8npp"); // invalid length
+ assertInvalidAddress("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"); // not a nano address
+ assertInvalidAddress("just completely wrong"); // not an address
+ }
+
+}