diff --git a/src/main/java/jota/IotaAPIProxy.java b/src/main/java/jota/IotaAPIProxy.java index 50a3a70..b742299 100644 --- a/src/main/java/jota/IotaAPIProxy.java +++ b/src/main/java/jota/IotaAPIProxy.java @@ -2,6 +2,7 @@ package jota; import jota.dto.request.*; import jota.dto.response.*; +import jota.utils.IotaAPIUtils; import okhttp3.OkHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/jota/dto/response/GetNewAddressResponse.java b/src/main/java/jota/dto/response/GetNewAddressResponse.java index a9216d2..dab7d86 100644 --- a/src/main/java/jota/dto/response/GetNewAddressResponse.java +++ b/src/main/java/jota/dto/response/GetNewAddressResponse.java @@ -7,4 +7,10 @@ public class GetNewAddressResponse extends AbstractResponse { public String getAddress() { return address; } + + public static GetNewAddressResponse create(String address) { + GetNewAddressResponse res = new GetNewAddressResponse(); + res.address = address; + return res; + } } diff --git a/src/main/java/jota/dto/response/GetTransfersResponse.java b/src/main/java/jota/dto/response/GetTransfersResponse.java index d96219b..4046ad4 100644 --- a/src/main/java/jota/dto/response/GetTransfersResponse.java +++ b/src/main/java/jota/dto/response/GetTransfersResponse.java @@ -15,8 +15,8 @@ public class GetTransfersResponse extends AbstractResponse { private String timestamp; private String address; private String hash; - private String persistence; - private String value; + private Integer persistence; + private long value; public String getAddress() { return address; @@ -26,7 +26,7 @@ public class GetTransfersResponse extends AbstractResponse { return hash; } - public String getPersistence() { + public Integer getPersistence() { return persistence; } @@ -34,7 +34,7 @@ public class GetTransfersResponse extends AbstractResponse { return timestamp; } - public String getValue() { + public long getValue() { return value; } diff --git a/src/main/java/jota/utils/Converter.java b/src/main/java/jota/utils/Converter.java new file mode 100644 index 0000000..be5313f --- /dev/null +++ b/src/main/java/jota/utils/Converter.java @@ -0,0 +1,128 @@ +package jota.utils; + +import java.util.Arrays; + +public class Converter { + + public static final int RADIX = 3; + public static final int MAX_TRIT_VALUE = (RADIX - 1) / 2, MIN_TRIT_VALUE = -MAX_TRIT_VALUE; + + public static final int NUMBER_OF_TRITS_IN_A_BYTE = 5; + public static final int NUMBER_OF_TRITS_IN_A_TRYTE = 3; + public static final String TRYTE_ALPHABET = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + static final int[][] BYTE_TO_TRITS_MAPPINGS = new int[243][]; + static final int[][] TRYTE_TO_TRITS_MAPPINGS = new int[27][]; + + static { + + final int[] trits = new int[NUMBER_OF_TRITS_IN_A_BYTE]; + + for (int i = 0; i < 243; i++) { + BYTE_TO_TRITS_MAPPINGS[i] = Arrays.copyOf(trits, NUMBER_OF_TRITS_IN_A_BYTE); + increment(trits, NUMBER_OF_TRITS_IN_A_BYTE); + } + + for (int i = 0; i < 27; i++) { + TRYTE_TO_TRITS_MAPPINGS[i] = Arrays.copyOf(trits, NUMBER_OF_TRITS_IN_A_TRYTE); + increment(trits, NUMBER_OF_TRITS_IN_A_TRYTE); + } + } + + public static byte[] bytes(final int[] trits, final int offset, final int size) { + + final byte[] bytes = new byte[(size + NUMBER_OF_TRITS_IN_A_BYTE - 1) / NUMBER_OF_TRITS_IN_A_BYTE]; + for (int i = 0; i < bytes.length; i++) { + + int value = 0; + for (int j = (size - i * NUMBER_OF_TRITS_IN_A_BYTE) < 5 ? (size - i * NUMBER_OF_TRITS_IN_A_BYTE) : NUMBER_OF_TRITS_IN_A_BYTE; j-- > 0; ) { + value = value * RADIX + trits[offset + i * NUMBER_OF_TRITS_IN_A_BYTE + j]; + } + bytes[i] = (byte) value; + } + + return bytes; + } + + public static byte[] bytes(final int[] trits) { + return bytes(trits, 0, trits.length); + } + + public static void getTrits(final byte[] bytes, final int[] trits) { + + int offset = 0; + for (int i = 0; i < bytes.length && offset < trits.length; i++) { + System.arraycopy(BYTE_TO_TRITS_MAPPINGS[bytes[i] < 0 ? (bytes[i] + BYTE_TO_TRITS_MAPPINGS.length) : bytes[i]], 0, trits, offset, trits.length - offset < NUMBER_OF_TRITS_IN_A_BYTE ? (trits.length - offset) : NUMBER_OF_TRITS_IN_A_BYTE); + offset += NUMBER_OF_TRITS_IN_A_BYTE; + } + while (offset < trits.length) { + trits[offset++] = 0; + } + } + + public static int[] trits(final String trytes) { + + final int[] trits = new int[trytes.length() * NUMBER_OF_TRITS_IN_A_TRYTE]; + for (int i = 0; i < trytes.length(); i++) { + System.arraycopy(TRYTE_TO_TRITS_MAPPINGS[TRYTE_ALPHABET.indexOf(trytes.charAt(i))], 0, trits, i * NUMBER_OF_TRITS_IN_A_TRYTE, NUMBER_OF_TRITS_IN_A_TRYTE); + } + + return trits; + } + + public static void copyTrits(final long value, final int[] destination, final int offset, final int size) { + + long absoluteValue = value < 0 ? -value : value; + for (int i = 0; i < size; i++) { + + int remainder = (int) (absoluteValue % RADIX); + absoluteValue /= RADIX; + if (remainder > MAX_TRIT_VALUE) { + + remainder = MIN_TRIT_VALUE; + absoluteValue++; + } + destination[offset + i] = remainder; + } + + if (value < 0) { + + for (int i = 0; i < size; i++) { + destination[offset + i] = -destination[offset + i]; + } + } + } + + public static String trytes(final int[] trits, final int offset, final int size) { + + StringBuilder trytes = new StringBuilder(); + for (int i = 0; i < (size + NUMBER_OF_TRITS_IN_A_TRYTE - 1) / NUMBER_OF_TRITS_IN_A_TRYTE; i++) { + + int j = trits[offset + i * 3] + trits[offset + i * 3 + 1] * 3 + trits[offset + i * 3 + 2] * 9; + if (j < 0) { + + j += TRYTE_ALPHABET.length(); + } + trytes.append(TRYTE_ALPHABET.charAt(j)); + } + return trytes.toString(); + } + + public static String trytes(final int[] trits) { + return trytes(trits, 0, trits.length); + } + + public static int tryteValue(final int[] trits, final int offset) { + return trits[offset] + trits[offset + 1] * 3 + trits[offset + 2] * 9; + } + + public static void increment(final int[] trits, final int size) { + + for (int i = 0; i < size; i++) { + if (++trits[i] > Converter.MAX_TRIT_VALUE) { + trits[i] = Converter.MIN_TRIT_VALUE; + } else { + break; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/jota/utils/Curl.java b/src/main/java/jota/utils/Curl.java new file mode 100644 index 0000000..b07236f --- /dev/null +++ b/src/main/java/jota/utils/Curl.java @@ -0,0 +1,59 @@ +package jota.utils; + +/** + * (c) 2016 Come-from-Beyond + *
+ * Curl belongs to the sponge function family.
+ */
+public class Curl {
+
+ public static final int HASH_LENGTH = 243;
+ private static final int STATE_LENGTH = 3 * HASH_LENGTH;
+
+ private static final int NUMBER_OF_ROUNDS = 27;
+ private static final int[] TRUTH_TABLE = {1, 0, -1, 1, -1, 0, -1, 1, 0};
+
+ private final int[] state = new int[STATE_LENGTH];
+
+ public void absorb(final int[] trits, int offset, int length) {
+
+ do {
+ System.arraycopy(trits, offset, state, 0, length < HASH_LENGTH ? length : HASH_LENGTH);
+ transform();
+ offset += HASH_LENGTH;
+ } while ((length -= HASH_LENGTH) > 0);
+ }
+
+ public int[] squeeze(final int[] trits, int offset, int length) {
+
+ do {
+ System.arraycopy(state, 0, trits, offset, length < HASH_LENGTH ? length : HASH_LENGTH);
+ transform();
+ offset += HASH_LENGTH;
+ } while ((length -= HASH_LENGTH) > 0);
+
+ return state;
+ }
+
+ private void transform() {
+
+ final int[] scratchpad = new int[STATE_LENGTH];
+ int scratchpadIndex = 0;
+ for (int round = 0; round < NUMBER_OF_ROUNDS; round++) {
+ System.arraycopy(state, 0, scratchpad, 0, STATE_LENGTH);
+ for (int stateIndex = 0; stateIndex < STATE_LENGTH; stateIndex++) {
+ state[stateIndex] = TRUTH_TABLE[scratchpad[scratchpadIndex] + scratchpad[scratchpadIndex += (scratchpadIndex < 365 ? 364 : -365)] * 3 + 4];
+ }
+ }
+ }
+
+ public void reset() {
+ for (int stateIndex = 0; stateIndex < STATE_LENGTH; stateIndex++) {
+ state[stateIndex] = 0;
+ }
+ }
+
+ public int[] getState() {
+ return state;
+ }
+}
diff --git a/src/main/java/jota/IotaAPIUtils.java b/src/main/java/jota/utils/IotaAPIUtils.java
similarity index 63%
rename from src/main/java/jota/IotaAPIUtils.java
rename to src/main/java/jota/utils/IotaAPIUtils.java
index 8de3f1b..dd47181 100644
--- a/src/main/java/jota/IotaAPIUtils.java
+++ b/src/main/java/jota/utils/IotaAPIUtils.java
@@ -1,4 +1,4 @@
-package jota;
+package jota.utils;
import jota.dto.response.GetBundleResponse;
import jota.dto.response.GetNewAddressResponse;
@@ -15,8 +15,14 @@ public class IotaAPIUtils {
private static final Logger log = LoggerFactory.getLogger(IotaAPIUtils.class);
- public static GetNewAddressResponse getNewAddress(final String seed, final Integer securityLevel) {
- throw new NotImplementedException("Not yet implemented");
+ public static GetNewAddressResponse getNewAddress(final String seed, final int index) {
+
+ final int[] key = Signing.key(Converter.trits(seed), index, 2);
+ final int[] digests = Signing.digests(key);
+ final int[] addressTrits = Signing.address(digests);
+ final String address = Converter.trytes(addressTrits);
+
+ return GetNewAddressResponse.create(address);
}
public static GetBundleResponse getBundle(final String transaction) {
diff --git a/src/main/java/jota/utils/Signing.java b/src/main/java/jota/utils/Signing.java
new file mode 100644
index 0000000..639a006
--- /dev/null
+++ b/src/main/java/jota/utils/Signing.java
@@ -0,0 +1,94 @@
+package jota.utils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class Signing {
+
+ static int[] key(int[] seed, int index, int length) {
+
+ final int[] subseed = seed;
+
+ for (int i = 0; i < index; i++) {
+ for (int j = 0; j < 243; j++) {
+ if (++subseed[j] > 1) {
+ subseed[j] = -1;
+ } else {
+ break;
+ }
+ }
+ }
+
+ Curl curl = new Curl();
+ //curl.absorb(subseed, state);
+ //curl.squeeze(subseed, state);
+ curl.absorb(subseed, 0, subseed.length);
+
+ List