CACHE = new ConcurrentHashMap<>();
public static Image getImage(byte[] pubKeyHash) {
@@ -45,23 +44,13 @@ private static Image getImage(ByteArray pubKeyHash, boolean useCache) {
if (useCache && CACHE.containsKey(pubKeyHash)) {
return CACHE.get(pubKeyHash);
}
- BigInteger bigInteger = new BigInteger(pubKeyHash.getBytes());
- Configuration configuration = new Configuration();
- VariableSizeHashing hashing = new VariableSizeHashing(configuration.getBucketSizes());
- byte[] data = hashing.createBuckets(bigInteger);
- Handle handle = HANDLE_FACTORY.calculateHandle(data);
- Image image = imageForHandle(handle, configuration);
+ BigInteger input = new BigInteger(pubKeyHash.getBytes());
+ int[] buckets = BucketEncoder.encode(input, BucketConfig.getBucketSizes());
+ String[] paths = BucketEncoder.toPaths(buckets, BucketConfig.getPathTemplates());
+ Image image = ImageUtil.composeImage(paths, SIZE, SIZE);
if (useCache && CACHE.size() < MAX_CACHE_SIZE) {
CACHE.put(pubKeyHash, image);
}
return image;
}
-
- private static Image imageForHandle(Handle handle, Configuration configuration) {
- long ts = System.currentTimeMillis();
- byte[] bucketValues = handle.bucketValues();
- String[] paths = configuration.convertToFacetParts(bucketValues);
- log.debug("Generated paths for CatHash image in {} ms", System.currentTimeMillis() - ts); // typically <1ms
- return ImageUtil.composeImage(paths, configuration.width(), configuration.height());
- }
}
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/Configuration.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/Configuration.java
deleted file mode 100644
index 44b37be2de..0000000000
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/Configuration.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.desktop.components.cathash;
-
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-public class Configuration {
- private final static String ROOT = "";
-
- private static final int BG0_COUNT = 15;
- private static final int BG1_COUNT = 15;
- private static final int EARS0_COUNT = 15;
- private static final int EARS1_COUNT = 2;
- private static final int FACE0_COUNT = 15;
- private static final int FACE1_COUNT = 8;
- private static final int EYES0_COUNT = 15;
- private static final int NOSE0_COUNT = 5;
- private static final int WHISKERS0_COUNT = 6;
-
- private final static int BUCKET_COUNT = 9;
- private final static int FACET_COUNT = 9;
-
- private final static byte[] BUCKET_SIZES = new byte[]{BG0_COUNT, BG1_COUNT, EARS0_COUNT, EARS1_COUNT, FACE0_COUNT,
- FACE1_COUNT, EYES0_COUNT, NOSE0_COUNT, WHISKERS0_COUNT};
-
- private final static String[] FACET_PATH_TEMPLATES;
-
- static {
- String postFix = ".png";
- FACET_PATH_TEMPLATES = new String[]{
- ROOT + "bg0/#ITEM#" + postFix,
- ROOT + "bg1/#ITEM#" + postFix,
- ROOT + "ears0/#ITEM#" + postFix,
- ROOT + "ears1/#ITEM#" + postFix,
- ROOT + "face0/#ITEM#" + postFix,
- ROOT + "face1/#ITEM#" + postFix,
- ROOT + "eyes0/#ITEM#" + postFix,
- ROOT + "nose0/#ITEM#" + postFix,
- ROOT + "whiskers0/#ITEM#" + postFix,
- };
- }
-
- public String[] convertToFacetParts(byte[] bucketValues) {
- if (bucketValues.length != BUCKET_COUNT) {
- throw new IllegalArgumentException();
- }
-
- String[] paths = new String[FACET_COUNT];
- for (int facet = 0; facet < FACET_COUNT; facet++) {
- int bucketValue = bucketValues[facet];
- paths[facet] = generatePath(FACET_PATH_TEMPLATES[facet], bucketValue);
- }
- return paths;
- }
-
- private String generatePath(String facetPathTemplate, int bucketValue) {
- return facetPathTemplate.replaceAll("#ITEM#", String.format("%02d", bucketValue));
- }
-
- public byte[] getBucketSizes() {
- return BUCKET_SIZES;
- }
-
- public int width() {
- return 300;
- }
-
- public int height() {
- return 300;
- }
-}
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/Handle.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/Handle.java
deleted file mode 100644
index 7c057457b4..0000000000
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/Handle.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.desktop.components.cathash;
-
-public final class Handle {
- private final long value;
-
- Handle(long v) {
- this.value = v;
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- @Override
- public boolean equals(Object o) {
- return super.equals(o);
- }
-
- @Override
- public String toString() {
- return String.format("%016X", value);
- }
-
- @Override
- public int hashCode() {
- return (int) value;
- }
-
- public byte[] bucketValues() {
- return HandleFactory.bucketValues(this.value);
- }
-}
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/HandleFactory.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/HandleFactory.java
deleted file mode 100644
index fcb19fa693..0000000000
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/HandleFactory.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.desktop.components.cathash;
-
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-public class HandleFactory {
-
- public Handle calculateHandle(byte[] data) {
- return new Handle(calculateHandleValue(data));
- }
-
- /**
- * Encodes an array of bytes (data) into a single long value, which serves as a compact, unique identifier
- * (or "handle") for a set of values. Each byte in the array represents a "nibble" (a 4-bit value), and the function
- * ensures that these values, along with the length of the data, are packed into the returned long.
- *
- * @param data The distributed hash over the buckets
- * @return val The composite handle value, which encodes both the sequence of nibbles and the length of the data array
- */
- static long calculateHandleValue(byte[] data) {
- // Check if the input array exceeds the maximum length of 14 bytes.
- // This limit ensures that the data can be encoded into a 64-bit long value without overflow.
- // Since 8 bits are reserved for length encoding only 56 bits can be used (14 * 4)
- // This means that the maximum number of buckets that we can have is 14
- if (data.length > 14) {
- throw new IllegalArgumentException();
- }
-
- long val = 0;
- for (int i = 0; i < data.length; i++) {
- int nibble = data[i];
-
- // Validate that the current nibble does not exceed the maximum value of 15 (0xF), ensuring it's a
- // valid 4-bit value.
- // Each nibble uses 4 bits, therefore we can only encode 2^4 (0..15) possibilities
- // (i.e. max size per bucket is 15, which represent 16 images)
- if (nibble > 15) { // 0xf
- throw new IllegalArgumentException(String.format("nibble to large @%d: %02X", i, nibble));
- }
-
- // Shift the current handle value 4 bits to the left to make room for the new nibble. This operation
- // progressively builds up the handle value from its constituent nibbles.
- val <<= 4;
-
- // Incorporate the current nibble into the lowest 4 bits of the handle value.
- val |= nibble;
- }
-
- // After processing all nibbles, encode the length of the data array into the handle value.
- // This is achieved by shifting the length leftward by (14 * 4) bits (56 bits), which positions the length
- // information in the upper 8 bits of the 64-bit long value. This ensures the length can be retrieved from the
- // handle and also contributes to the uniqueness of the handle.
- val |= ((long) data.length) << (14 * 4);
- return val;
- }
-
- static byte getNibbleAt(long value, int index) {
- if (index < 0 || index > 15) {
- throw new IllegalArgumentException(String.format("index @%d", index));
- }
-
- long mask = (long) 0xf << (index * 4);
- long maskedValue = (value & mask);
-
- return (byte) (maskedValue >> index * 4);
- }
-
- static int getSize(long value) {
- return getNibbleAt(value, 14);
- }
-
- public static byte[] bucketValues(long handle) {
- int buckets = getSize(handle);
- byte[] values = new byte[buckets];
- for (int i = 0; i < buckets; i++) {
- values[buckets - i - 1] = getNibbleAt(handle, i);
- }
- return values;
- }
-}
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/VariableSizeHashing.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/VariableSizeHashing.java
deleted file mode 100644
index e117b036b9..0000000000
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/VariableSizeHashing.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.desktop.components.cathash;
-
-import java.math.BigInteger;
-import java.util.UUID;
-
-/**
- * "Hash" a big integer (expected: hash or uuid) into buckets. The goal is to deterministically
- * "repack" the randomness of the hash into the bucket.
- *
- * Each bucket is defined by a maximum value. The implementation guarantees that the values in bucket n is in the
- * range 0..(bucketSize[n]-1).
- */
-public class VariableSizeHashing {
- private final byte[] bucketSizes;
-
- public VariableSizeHashing(byte[] bucketSizes) {
- this.bucketSizes = bucketSizes;
- }
-
- static BigInteger uuidToBigInteger(UUID uuid) {
- return BigInteger.valueOf(uuid.getMostSignificantBits()).shiftLeft(64).add(BigInteger.valueOf(uuid.getLeastSignificantBits()));
- }
-
- public byte[] createBuckets(UUID uuid) {
- return createBuckets(uuidToBigInteger(uuid));
- }
-
- /**
- * Takes the hash value and distributes it over the buckets.
- *
- * Assumption: the value of hash is (much) larger than `16^bucketSizes.length` and uniformly distributed (random)
- *
- * @param hash Any BigInteger that is to be split up in buckets according to the bucket configuration #bucketSizes.
- * @return buckets The distributed hash
- */
- public byte[] createBuckets(BigInteger hash) {
- int currentBucket = 0;
- byte[] ret = new byte[this.bucketSizes.length];
-
- while (currentBucket < this.bucketSizes.length) {
- BigInteger[] divisorReminder = hash.divideAndRemainder(BigInteger.valueOf(bucketSizes[currentBucket]));
-
- hash = divisorReminder[0];
- long reminder = divisorReminder[1].longValue();
-
- ret[currentBucket] = (byte) Math.abs(reminder % bucketSizes[currentBucket]);
-
- currentBucket += 1;
- }
-
- return ret;
- }
-}
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/overlay/onboarding/create_profile/CreateProfileController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/overlay/onboarding/create_profile/CreateProfileController.java
index 29b91ccd3e..27b3ab1ac1 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/overlay/onboarding/create_profile/CreateProfileController.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/overlay/onboarding/create_profile/CreateProfileController.java
@@ -22,8 +22,8 @@
import bisq.desktop.common.threading.UIThread;
import bisq.desktop.common.view.Controller;
import bisq.desktop.common.view.Navigation;
-import bisq.desktop.components.overlay.Popup;
import bisq.desktop.components.cathash.CatHash;
+import bisq.desktop.components.overlay.Popup;
import bisq.desktop.overlay.OverlayController;
import bisq.i18n.Res;
import bisq.identity.IdentityService;
diff --git a/common/src/main/java/bisq/common/util/MathUtils.java b/common/src/main/java/bisq/common/util/MathUtils.java
index da046d4177..16428cf4ec 100644
--- a/common/src/main/java/bisq/common/util/MathUtils.java
+++ b/common/src/main/java/bisq/common/util/MathUtils.java
@@ -108,4 +108,8 @@ public static long bounded(long lowerBound, long upperBound, long value) {
"lowerBound must not be larger than upperBound");
return Math.min(Math.max(value, lowerBound), upperBound);
}
+
+ public static double getLog2(long value) {
+ return Math.log(value) / Math.log(2);
+ }
}