From 9173bb9fb6c71500b065e27b590987edc053347a Mon Sep 17 00:00:00 2001 From: Oliver Nitzschke Date: Wed, 7 Dec 2016 15:19:24 +0100 Subject: [PATCH 01/12] updated tests (#8) --- src/main/java/jota/utils/IotaAPIUtils.java | 10 ++------ src/test/java/jota/AddressGenerationTest.java | 20 --------------- src/test/java/jota/IotaAPIProxyTest.java | 25 ++++++++----------- 3 files changed, 13 insertions(+), 42 deletions(-) delete mode 100644 src/test/java/jota/AddressGenerationTest.java diff --git a/src/main/java/jota/utils/IotaAPIUtils.java b/src/main/java/jota/utils/IotaAPIUtils.java index d53fba5..3da5c2d 100644 --- a/src/main/java/jota/utils/IotaAPIUtils.java +++ b/src/main/java/jota/utils/IotaAPIUtils.java @@ -1,11 +1,10 @@ package jota.utils; +import jota.dto.response.GetBundleResponse; import org.apache.commons.lang3.NotImplementedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jota.dto.response.GetBundleResponse; - /** * Client Side computation service * @@ -26,14 +25,9 @@ public class IotaAPIUtils { public static String newAddress(String seed, int index, boolean checksum) { final int[] key = Signing.key(Converter.trits(seed), index, 2); - log.debug("key Length = {}", key.length ); - final int[] digests = Signing.digests(key); - log.debug("digests Length = {}", digests.length ); - final int[] addressTrits = Signing.address(digests); - log.debug("addressTrits Length = {}", addressTrits.length ); - + String address = Converter.trytes(addressTrits); if (checksum) { diff --git a/src/test/java/jota/AddressGenerationTest.java b/src/test/java/jota/AddressGenerationTest.java deleted file mode 100644 index 3dfddea..0000000 --- a/src/test/java/jota/AddressGenerationTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package jota; - -import jota.utils.IotaAPIUtils; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -/** - * Created by Adrian on 02.12.2016. - */ -public class AddressGenerationTest { - private static String TEST_SEED = "ZEB99QTOMYDSKIFCXTLTVSWQFKO9CRKQKMRDR9HWVOVSGZMWPFQIMSCXXWUULHD9MZKMFJZAYZHZYA9VZ"; - private static String FIRST_ADDRESS = "LCZXWAQUHBXST9IEPPMJICTWLKJA9HVASXWDIRCVNM9TUAGZY9SRRJLZMZQIZKBAESXXNABFATUAYQYYW"; - - @Test - public void shouldAddChecksum() { - assertEquals(IotaAPIUtils.newAddress(TEST_SEED,0, false),FIRST_ADDRESS); - } - -} diff --git a/src/test/java/jota/IotaAPIProxyTest.java b/src/test/java/jota/IotaAPIProxyTest.java index 6a153ab..9b16cda 100644 --- a/src/test/java/jota/IotaAPIProxyTest.java +++ b/src/test/java/jota/IotaAPIProxyTest.java @@ -3,8 +3,6 @@ package jota; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import jota.dto.response.*; -import jota.utils.IotaAPIUtils; - import org.hamcrest.core.Is; import org.hamcrest.core.IsNull; import org.junit.Before; @@ -12,7 +10,6 @@ import org.junit.Test; import java.util.Collections; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; /** @@ -78,19 +75,19 @@ public class IotaAPIProxyTest { @Test public void shouldFindTransactionsByApprovees() { - FindTransactionResponse trans = proxy.findTransactionsByApprovees(new String[]{"123ABC"}); + FindTransactionResponse trans = proxy.findTransactionsByApprovees(new String[]{TEST_HASH}); assertThat(trans, IsNull.notNullValue()); } @Test public void shouldFindTransactionsByBundles() { - FindTransactionResponse trans = proxy.findTransactionsByBundles(new String[]{"123ABC"}); + FindTransactionResponse trans = proxy.findTransactionsByBundles(TEST_HASH); assertThat(trans, IsNull.notNullValue()); } @Test public void shouldFindTransactionsByDigests() { - FindTransactionResponse trans = proxy.findTransactionsByDigests(new String[]{"123ABC"}); + FindTransactionResponse trans = proxy.findTransactionsByDigests(TEST_HASH); assertThat(trans, IsNull.notNullValue()); } @@ -100,13 +97,13 @@ public class IotaAPIProxyTest { @Test public void shouldGetTrytes() { GetTrytesResponse res = proxy.getTrytes(TEST_HASH); - assertThat(res, IsNull.nullValue()); + assertThat(res, IsNull.notNullValue()); + } @Test public void shouldGetInclusionStates() { - GetInclusionStateResponse res = proxy.getInclusionStates(new String[]{TEST_ADDRESS_WITH_CHECKSUM}, - new String[]{"123"}); + GetInclusionStateResponse res = proxy.getInclusionStates(new String[]{TEST_ADDRESS_WITH_CHECKSUM}, new String[]{"DNSBRJWNOVUCQPILOQIFDKBFJMVOTGHLIMLLRXOHFTJZGRHJUEDAOWXQRYGDI9KHYFGYDWQJZKX999999"}); assertThat(res, IsNull.notNullValue()); } @@ -118,7 +115,7 @@ public class IotaAPIProxyTest { @Test public void shouldGetBalances() { - GetBalancesResponse res = proxy.getBalances(100, new String[]{"HBBYKAKTILIPVUKFOTSLHGENPTXYBNKXZFQFR9VQFWNBMTQNRVOUKPVPRNBSZVVILMAFBKOTBLGLWLOHQ"}); + GetBalancesResponse res = proxy.getBalances(100, new String[]{TEST_ADDRESS_WITH_CHECKSUM}); System.err.println(res); assertThat(res, IsNull.notNullValue()); } @@ -129,9 +126,9 @@ public class IotaAPIProxyTest { assertThat(proxy, IsNull.notNullValue()); } - /*@Test TODO Fix me + @Test public void shouldCreateANewAddress() { - final GetNewAddressResponse res = proxy.newAddress(TEST_SEED, 4, false); - assertThat(res.getAddress(), Is.is("GBPQGDMZ99FRNUBLCCIAXOEWNED9T9AMEHCGMMMFYTP9VINCVSNPAXUXBHQ9DIPTOOTP9XXUAUBDBMWMP")); - }*/ + final GetNewAddressResponse res = proxy.getNewAddress(TEST_SEED, 0, false, 1, false); + assertThat(res.getAddress(), Is.is(Collections.singletonList(TEST_ADDRESS_WITHOUT_CHECKSUM))); + } } \ No newline at end of file From 9dc1947234492fa81335c7b5c6a255392baf2cdb Mon Sep 17 00:00:00 2001 From: davassi Date: Wed, 7 Dec 2016 17:34:09 +0100 Subject: [PATCH 02/12] some refactoring --- src/main/java/jota/IotaAPIProxy.java | 25 +++++++------------- src/main/java/jota/{utils => pow}/Curl.java | 2 +- src/main/java/jota/utils/Checksum.java | 2 ++ src/main/java/jota/utils/InputValidator.java | 2 +- src/main/java/jota/utils/Signing.java | 2 ++ 5 files changed, 14 insertions(+), 19 deletions(-) rename src/main/java/jota/{utils => pow}/Curl.java (98%) diff --git a/src/main/java/jota/IotaAPIProxy.java b/src/main/java/jota/IotaAPIProxy.java index 617de2e..5ce6806 100644 --- a/src/main/java/jota/IotaAPIProxy.java +++ b/src/main/java/jota/IotaAPIProxy.java @@ -208,48 +208,39 @@ public class IotaAPIProxy { * @param returnAll If true, it returns all addresses which were deterministically generated (until findTransactions returns null) * @return an array of strings with the specifed number of addresses */ - public GetNewAddressResponse getNewAddress(final String seed, final int index, final boolean checksum, final int total, final boolean returnAll) { final List allAddresses = new ArrayList<>(); - // Case 1: total - // + // If total number of addresses to generate is supplied, simply generate // and return the list of all addresses - if (total != 0) { - // Increase index with each iteration for (int i = index; i < index + total; i++) { allAddresses.add(IotaAPIUtils.newAddress(seed, i, checksum)); } return GetNewAddressResponse.create(allAddresses); } - - // Case 2: no total provided - // - // Continue calling findTransactions to see if address was already created - // if null, return list of addresses - + // No total provided: Continue calling findTransactions to see if address was + // already created if null, return list of addresses for (int i = index; ; i++) { - String newAddress = IotaAPIUtils.newAddress(seed, i, checksum); - + + final String newAddress = IotaAPIUtils.newAddress(seed, i, checksum); final FindTransactionResponse response = findTransactionsByAddresses(new String[]{newAddress}); allAddresses.add(newAddress); - if (response.getHashes().length == 0) { break; } } - // If returnAll, return list of allAddresses - // else return only the last address that was generated + // If !returnAll return only the last address that was generated if (!returnAll) { allAddresses.subList(0, allAddresses.size()-1).clear(); } - return GetNewAddressResponse.create(allAddresses); } + + public static class Builder { diff --git a/src/main/java/jota/utils/Curl.java b/src/main/java/jota/pow/Curl.java similarity index 98% rename from src/main/java/jota/utils/Curl.java rename to src/main/java/jota/pow/Curl.java index 2c296e8..d72ed45 100644 --- a/src/main/java/jota/utils/Curl.java +++ b/src/main/java/jota/pow/Curl.java @@ -1,4 +1,4 @@ -package jota.utils; +package jota.pow; /** * (c) 2016 Come-from-Beyond diff --git a/src/main/java/jota/utils/Checksum.java b/src/main/java/jota/utils/Checksum.java index 10a49de..fe22f5a 100644 --- a/src/main/java/jota/utils/Checksum.java +++ b/src/main/java/jota/utils/Checksum.java @@ -2,6 +2,8 @@ package jota.utils; import org.apache.commons.lang3.StringUtils; +import jota.pow.Curl; + /** * Created by pinpong on 02.12.16. */ diff --git a/src/main/java/jota/utils/InputValidator.java b/src/main/java/jota/utils/InputValidator.java index ed5f2ef..c1ddd2a 100644 --- a/src/main/java/jota/utils/InputValidator.java +++ b/src/main/java/jota/utils/InputValidator.java @@ -17,7 +17,7 @@ public class InputValidator { return true; } - public static boolean isTrytes(String trytes, int length) { + public static boolean isTrytes(final String trytes, final int length) { return trytes.matches("^[A-Z9]{" + (length == 0 ? "0," : length) + "}$"); } } diff --git a/src/main/java/jota/utils/Signing.java b/src/main/java/jota/utils/Signing.java index 025529e..a5e2553 100644 --- a/src/main/java/jota/utils/Signing.java +++ b/src/main/java/jota/utils/Signing.java @@ -4,6 +4,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import jota.pow.Curl; + public class Signing { static int[] key(int[] seed, int index, int length) { From 21ddac09d0b93078493ab3ce0408e50a577edaa2 Mon Sep 17 00:00:00 2001 From: Oliver Nitzschke Date: Wed, 14 Dec 2016 11:06:06 +0100 Subject: [PATCH 03/12] updated and added new Utils (#9) * added SeedRandomGenerator * extended IotaUnitConverter * minor * added isArrayOfHashes * renamed constants * added error package * added transactionObject * fixed IotaUnitConverter --- .../java/jota/error/ArgumentException.java | 10 ++ src/main/java/jota/error/BaseException.java | 51 +++++++ .../jota/error/NotEnoughBalanceException.java | 10 ++ src/main/java/jota/model/Transaction.java | 141 ++++++++++++------ src/main/java/jota/utils/Checksum.java | 4 +- src/main/java/jota/utils/Constants.java | 6 +- src/main/java/jota/utils/Converter.java | 63 +++++++- src/main/java/jota/utils/InputValidator.java | 22 ++- .../java/jota/utils/IotaUnitConverter.java | 53 ++++++- .../java/jota/utils/SeedRandomGenerator.java | 20 +++ src/main/java/jota/utils/TrytesConverter.java | 8 +- src/test/java/jota/IotaUnitConverterTest.java | 20 +++ .../java/jota/SeedRandomGeneratorTest.java | 22 +++ 13 files changed, 368 insertions(+), 62 deletions(-) create mode 100644 src/main/java/jota/error/ArgumentException.java create mode 100644 src/main/java/jota/error/BaseException.java create mode 100644 src/main/java/jota/error/NotEnoughBalanceException.java create mode 100644 src/main/java/jota/utils/SeedRandomGenerator.java create mode 100644 src/test/java/jota/SeedRandomGeneratorTest.java diff --git a/src/main/java/jota/error/ArgumentException.java b/src/main/java/jota/error/ArgumentException.java new file mode 100644 index 0000000..759b694 --- /dev/null +++ b/src/main/java/jota/error/ArgumentException.java @@ -0,0 +1,10 @@ +package jota.error; + +/** + * Created by Adrian on 09.12.2016. + */ +public class ArgumentException extends BaseException { + public ArgumentException() { + super("wrong arguments passed to function"); + } +} diff --git a/src/main/java/jota/error/BaseException.java b/src/main/java/jota/error/BaseException.java new file mode 100644 index 0000000..b1eb210 --- /dev/null +++ b/src/main/java/jota/error/BaseException.java @@ -0,0 +1,51 @@ +package jota.error; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; + +/** + * Created by Adrian on 09.12.2016. + */ +public class BaseException extends Exception { + protected Collection messages; + + public BaseException(String msg) { + super(msg); + } + + + public BaseException(String msg, Exception cause) { + super(msg, cause); + } + + + public BaseException(Collection messages) { + super(); + this.messages = messages; + } + + + public BaseException(Collection messages, Exception cause) { + super(cause); + this.messages = messages; + } + + @Override + public String getMessage() { + String msg; + + if (this.messages != null && !this.messages.isEmpty()) { + msg = "["; + + for (String message : this.messages) { + msg += message + ","; + } + + msg = StringUtils.removeEnd(msg, ",") + "]"; + + } else msg = super.getMessage(); + + return msg; + } +} diff --git a/src/main/java/jota/error/NotEnoughBalanceException.java b/src/main/java/jota/error/NotEnoughBalanceException.java new file mode 100644 index 0000000..c7099d6 --- /dev/null +++ b/src/main/java/jota/error/NotEnoughBalanceException.java @@ -0,0 +1,10 @@ +package jota.error; + +/** + * Created by Adrian on 09.12.2016. + */ +public class NotEnoughBalanceException extends BaseException { + public NotEnoughBalanceException() { + super("not enough balance dude"); + } +} diff --git a/src/main/java/jota/model/Transaction.java b/src/main/java/jota/model/Transaction.java index 4cf5e89..8a74f5a 100644 --- a/src/main/java/jota/model/Transaction.java +++ b/src/main/java/jota/model/Transaction.java @@ -7,36 +7,38 @@ import org.apache.commons.lang3.builder.ToStringStyle; * Created by pinpong on 02.12.16. */ public class Transaction { - - private String signatureMessageChunk; - private String index; - private String approvalNonce; private String hash; - private String digest; - private String type; - private String timestamp; - private String trunkTransaction; - private String branchTransaction; - private String signatureNonce; + private String signatureFragments; private String address; private String value; + private String tag; + private String timestamp; + private String currentIndex; + private String lastIndex; private String bundle; + private String trunkTransaction; + private String branchTransaction; + private String nonce; + private Boolean persistence; - public Transaction(String signatureMessageChunk, String index, String approvalNonce, String hash, String digest, String type, String timestamp, String trunkTransaction, String branchTransaction, String signatureNonce, String address, String value, String bundle) { + public Transaction() { + + } + + public Transaction(String signatureFragments, String currentIndex, String lastIndex, String nonce, String hash, String tag, String timestamp, String trunkTransaction, String branchTransaction, String address, String value, String bundle) { this.hash = hash; - this.type = type; - this.signatureMessageChunk = signatureMessageChunk; - this.digest = digest; + this.tag = tag; + this.signatureFragments = signatureFragments; this.address = address; this.value = value; this.timestamp = timestamp; - this.index = index; + this.currentIndex = currentIndex; + this.lastIndex = lastIndex; this.bundle = bundle; - this.signatureNonce = signatureNonce; - this.approvalNonce = approvalNonce; this.trunkTransaction = trunkTransaction; this.branchTransaction = branchTransaction; + this.nonce = nonce; } @Override @@ -44,56 +46,107 @@ public class Transaction { return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); } - public String getValue() { - return value; + public String getHash() { + return hash; } - public String getDigest() { - return digest; + public void setHash(String hash) { + this.hash = hash; } - public String getTrunkTransaction() { - return trunkTransaction; + public String getSignatureFragments() { + return signatureFragments; } - public String getTimestamp() { - return timestamp; - } - - public String getSignatureNonce() { - return signatureNonce; - } - - public String getType() { - return type; + public String setSignatureFragments(String signatureFragments) { + return this.signatureFragments = signatureFragments; } public String getAddress() { return address; } - public String getApprovalNonce() { - return approvalNonce; + public void setAddress(String address) { + this.address = address; } - public String getBranchTransaction() { - return branchTransaction; + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public String getCurrentIndex() { + return currentIndex; + } + + public String setCurrentIndex(String currentIndex) { + return this.currentIndex = currentIndex; + } + + public String getLastIndex() { + return lastIndex; + } + + public String setLastIndex(String lastIndex) { + return this.lastIndex = lastIndex; } public String getBundle() { return bundle; } - public String getHash() { - return hash; + public void setBundle(String bundle) { + this.bundle = bundle; } - public String getIndex() { - return index; + public String getTrunkTransaction() { + return trunkTransaction; } - public String getSignatureMessageChunk() { - return signatureMessageChunk; + public void setTrunkTransaction(String trunkTransaction) { + this.trunkTransaction = trunkTransaction; } -} + public String getBranchTransaction() { + return branchTransaction; + } + + public void setBranchTransaction(String branchTransaction) { + this.branchTransaction = branchTransaction; + } + + public String getNonce() { + return nonce; + } + + public void setNonce(String nonce) { + this.nonce = nonce; + } + + public Boolean getPersistence() { + return persistence; + } + + public void setPersistence(Boolean persistence) { + this.persistence = persistence; + } +} \ No newline at end of file diff --git a/src/main/java/jota/utils/Checksum.java b/src/main/java/jota/utils/Checksum.java index fe22f5a..6bf83f5 100644 --- a/src/main/java/jota/utils/Checksum.java +++ b/src/main/java/jota/utils/Checksum.java @@ -24,7 +24,7 @@ public class Checksum { } private static String getAddress(String addressWithChecksum) { - return addressWithChecksum.substring(0, Constants.addressLengthWithoutChecksum); + return addressWithChecksum.substring(0, Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM); } public static boolean isValidChecksum(String addressWithChecksum) { @@ -34,7 +34,7 @@ public class Checksum { } private static boolean isAddressWithChecksum(String addressWithChecksum) { - return InputValidator.checkAddress(addressWithChecksum) && addressWithChecksum.length() == Constants.addressLengthWithChecksum; + return InputValidator.checkAddress(addressWithChecksum) && addressWithChecksum.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM; } public static String calculateChecksum(String address) { diff --git a/src/main/java/jota/utils/Constants.java b/src/main/java/jota/utils/Constants.java index 997b296..2b9d4a5 100644 --- a/src/main/java/jota/utils/Constants.java +++ b/src/main/java/jota/utils/Constants.java @@ -7,7 +7,9 @@ public class Constants { public static final String TRYTE_ALPHABET = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - public static int addressLengthWithoutChecksum = 81; - public static int addressLengthWithChecksum = 90; + public static final int SEED_LENGTH_MAX = 81; + + public static int ADDRESS_LENGTH_WITHOUT_CHECKSUM = 81; + public static int ADDRESS_LENGTH_WITH_CHECKSUM = 90; } diff --git a/src/main/java/jota/utils/Converter.java b/src/main/java/jota/utils/Converter.java index a43388f..0a94107 100644 --- a/src/main/java/jota/utils/Converter.java +++ b/src/main/java/jota/utils/Converter.java @@ -1,16 +1,19 @@ package jota.utils; +import jota.model.Transaction; +import jota.pow.Curl; + 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; + private static final int RADIX = 3; + private 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; - static final int[][] BYTE_TO_TRITS_MAPPINGS = new int[243][]; - static final int[][] TRYTE_TO_TRITS_MAPPINGS = new int[27][]; + private static final int NUMBER_OF_TRITS_IN_A_BYTE = 5; + private static final int NUMBER_OF_TRITS_IN_A_TRYTE = 3; + private static final int[][] BYTE_TO_TRITS_MAPPINGS = new int[243][]; + private static final int[][] TRYTE_TO_TRITS_MAPPINGS = new int[27][]; static { @@ -94,7 +97,7 @@ public class Converter { public static int[] copyTrits(final String input, final int[] destination) { for (int i = 0; i < input.length(); i++) { int index = Constants.TRYTE_ALPHABET.indexOf(input.charAt(i)); - destination[i * 3] = TRYTE_TO_TRITS_MAPPINGS [index][0]; + destination[i * 3] = TRYTE_TO_TRITS_MAPPINGS[index][0]; destination[i * 3 + 1] = TRYTE_TO_TRITS_MAPPINGS[index][1]; destination[i * 3 + 2] = TRYTE_TO_TRITS_MAPPINGS[index][2]; } @@ -124,6 +127,15 @@ public class Converter { return trits[offset] + trits[offset + 1] * 3 + trits[offset + 2] * 9; } + public static int value(final int[] trits) { + int value = 0; + + for (int i = trits.length; i-- > 0; ) { + value = value * 3 + trits[i]; + } + return value; + } + public static void increment(final int[] trits, final int size) { for (int i = 0; i < size; i++) { @@ -134,4 +146,41 @@ public class Converter { } } } + + public static Transaction transactionObject(String trytes) { + if (trytes == null) return null; + + // validity check + for (int i = 2279; i < 2295; i++) { + if (trytes.charAt(i) != '9') { + return null; + } + } + int[] transactionTrits = Converter.trits(trytes); + int[] hash = new int[90]; + + Curl curl = new Curl(); + + // generate the correct transaction hash + curl.reset(); + curl.absorb(transactionTrits, 0, transactionTrits.length); + curl.squeeze(hash, 0, hash.length); + + Transaction trx = new Transaction(); + + trx.setHash(Converter.trytes(hash)); + trx.setSignatureFragments(trytes.substring(0, 2187)); + trx.setAddress(trytes.substring(2187, 2268)); + trx.setValue("" + Converter.value(Arrays.copyOfRange(transactionTrits, 6804, 6837))); + trx.setTag(trytes.substring(2295, 2322)); + trx.setTimestamp("" + Converter.value(Arrays.copyOfRange(transactionTrits, 6966, 6993))); + trx.setCurrentIndex("" + Converter.value(Arrays.copyOfRange(transactionTrits, 6993, 7020))); + trx.setLastIndex("" + Converter.value(Arrays.copyOfRange(transactionTrits, 7020, 7047))); + trx.setBundle(trytes.substring(2349, 2430)); + trx.setTrunkTransaction(trytes.substring(2430, 2511)); + trx.setBranchTransaction(trytes.substring(2511, 2592)); + trx.setNonce(trytes.substring(2592, 2673)); + + return trx; + } } \ No newline at end of file diff --git a/src/main/java/jota/utils/InputValidator.java b/src/main/java/jota/utils/InputValidator.java index c1ddd2a..d658bd3 100644 --- a/src/main/java/jota/utils/InputValidator.java +++ b/src/main/java/jota/utils/InputValidator.java @@ -6,8 +6,8 @@ package jota.utils; public class InputValidator { public static boolean isAddress(String address) { - return (address.length() == Constants.addressLengthWithoutChecksum || - address.length() == Constants.addressLengthWithChecksum) && isTrytes(address, address.length()); + return (address.length() == Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM || + address.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM) && isTrytes(address, address.length()); } public static boolean checkAddress(String address) { @@ -20,4 +20,22 @@ public class InputValidator { public static boolean isTrytes(final String trytes, final int length) { return trytes.matches("^[A-Z9]{" + (length == 0 ? "0," : length) + "}$"); } + + public static boolean isArrayOfHashes(String[] hashes) { + if (hashes == null) return false; + + for (String hash : hashes) { + // Check if address with checksum + if (hash.length() == 90) { + if (!isTrytes(hash, 90)) { + return false; + } + } else { + if (!isTrytes(hash, 81)) { + return false; + } + } + } + return true; + } } diff --git a/src/main/java/jota/utils/IotaUnitConverter.java b/src/main/java/jota/utils/IotaUnitConverter.java index 57271e8..cdd4529 100644 --- a/src/main/java/jota/utils/IotaUnitConverter.java +++ b/src/main/java/jota/utils/IotaUnitConverter.java @@ -1,7 +1,9 @@ package jota.utils; +import java.text.DecimalFormat; + /** - * Created by pinpong on 30.11.16. + * Created by Sascha on 30.11.16. */ public class IotaUnitConverter { @@ -13,4 +15,53 @@ public class IotaUnitConverter { private static long convertUnits(long amount, IotaUnits toUnit) { return (long) (amount / Math.pow(10, toUnit.getValue())); } + + public static String convertRawIotaAmountToDisplayText(long amount) { + IotaUnits unit = findOptimalIotaUnitToDisplay(amount); + double amountInDisplayUnit = convertAmountTo(amount, unit); + return createAmountWithUnitDisplayText(amountInDisplayUnit, unit); + } + + public static double convertAmountTo(long amount, IotaUnits target) { + return amount / Math.pow(10, target.getValue()); + } + + private static String createAmountWithUnitDisplayText(double amountInUnit, IotaUnits unit) { + String result = createAmountDisplayText(amountInUnit, unit); + result += " " + unit.getUnit(); + return result; + } + + public static String createAmountDisplayText(double amountInUnit, IotaUnits unit) { + DecimalFormat df = new DecimalFormat("##0.##################"); + String result = ""; + // display unit as integer if value is between 1-999 or in decimal format + result += unit == IotaUnits.IOTA ? (long) amountInUnit : df.format(amountInUnit); + return result; + } + + public static IotaUnits findOptimalIotaUnitToDisplay(long amount) { + int length = String.valueOf(amount).length(); + + if (amount < 0) // do not count "-" sign + length -= 1; + + IotaUnits units = IotaUnits.IOTA; + + if (length >= 1 && length <= 3) { + units = IotaUnits.IOTA; + } else if (length > 3 && length <= 6) { + units = IotaUnits.KILO_IOTA; + } else if (length > 6 && length <= 9) { + units = IotaUnits.MEGA_IOTA; + } else if (length > 9 && length <= 12) { + units = IotaUnits.GIGA_IOTA; + } else if (length > 12 && length <= 15) { + units = IotaUnits.TERA_IOTA; + } else if (length > 15 && length <= 18) { + units = IotaUnits.PETA_IOTA; + } + return units; + } + } diff --git a/src/main/java/jota/utils/SeedRandomGenerator.java b/src/main/java/jota/utils/SeedRandomGenerator.java new file mode 100644 index 0000000..8e702cb --- /dev/null +++ b/src/main/java/jota/utils/SeedRandomGenerator.java @@ -0,0 +1,20 @@ +package jota.utils; + +import java.util.Random; + +/** + * Created by pinpong on 13.12.16. + */ +public class SeedRandomGenerator { + + public static String generateNewSeed() { + char[] chars = Constants.TRYTE_ALPHABET.toCharArray(); + StringBuilder builder = new StringBuilder(); + Random random = new Random(); + for (int i = 0; i < Constants.SEED_LENGTH_MAX; i++) { + char c = chars[random.nextInt(chars.length)]; + builder.append(c); + } + return builder.toString(); + } +} diff --git a/src/main/java/jota/utils/TrytesConverter.java b/src/main/java/jota/utils/TrytesConverter.java index 21f560e..5c5d843 100644 --- a/src/main/java/jota/utils/TrytesConverter.java +++ b/src/main/java/jota/utils/TrytesConverter.java @@ -70,7 +70,7 @@ public class TrytesConverter { public static String toString(String inputTrytes) { - String string = ""; + StringBuilder string = new StringBuilder(); for (int i = 0; i < inputTrytes.length(); i += 2) { // get a trytes pair @@ -82,9 +82,9 @@ public class TrytesConverter { String character = Character.toString((char) decimalValue); - string += character; + string.append(character); } - return string; + return string.toString(); } -} +} \ No newline at end of file diff --git a/src/test/java/jota/IotaUnitConverterTest.java b/src/test/java/jota/IotaUnitConverterTest.java index 5078ac5..81da852 100644 --- a/src/test/java/jota/IotaUnitConverterTest.java +++ b/src/test/java/jota/IotaUnitConverterTest.java @@ -35,4 +35,24 @@ public class IotaUnitConverterTest { public void shouldConvertUnitTiToPi() { assertEquals(IotaUnitConverter.convertUnits(1000, IotaUnits.TERA_IOTA, IotaUnits.PETA_IOTA), 1); } + + @Test + public void shouldFindOptimizeUnitToDisplay() { + assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1), IotaUnits.IOTA); + assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1000), IotaUnits.KILO_IOTA); + assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1000000), IotaUnits.MEGA_IOTA); + assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1000000000), IotaUnits.GIGA_IOTA); + assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1000000000000L), IotaUnits.TERA_IOTA); + assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1000000000000000L), IotaUnits.PETA_IOTA); + } + + @Test + public void shouldConvertRawIotaAmountToDisplayText() { + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1), "1 i"); + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000), "1 Ki"); + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000), "1 Mi" ); + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000), "1 Gi" ); + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000000L), "1 Ti"); + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000000000L), "1 Pi"); + } } diff --git a/src/test/java/jota/SeedRandomGeneratorTest.java b/src/test/java/jota/SeedRandomGeneratorTest.java new file mode 100644 index 0000000..b2ab6d2 --- /dev/null +++ b/src/test/java/jota/SeedRandomGeneratorTest.java @@ -0,0 +1,22 @@ +package jota; + +import jota.utils.Constants; +import jota.utils.InputValidator; +import jota.utils.SeedRandomGenerator; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Created by pinpong on 13.12.16. + */ +public class SeedRandomGeneratorTest { + + @Test + public void shouldGenerateNewSeed() { + + String generatedSeed = SeedRandomGenerator.generateNewSeed(); + assertEquals(InputValidator.isAddress(generatedSeed), true); + assertEquals(generatedSeed.length(), Constants.SEED_LENGTH_MAX); + } +} From b38e828dc293b861934cab50a7d95682bdad55d4 Mon Sep 17 00:00:00 2001 From: Gianluigi Davassi Date: Thu, 15 Dec 2016 11:23:04 +0100 Subject: [PATCH 04/12] updating (#10) From 548b311eedba40b4e971eb7efd7b1ca216cb3966 Mon Sep 17 00:00:00 2001 From: Gianluigi Davassi Date: Fri, 16 Dec 2016 16:01:28 +0100 Subject: [PATCH 05/12] Merging (#11) * added SeedRandomGenerator * extended IotaUnitConverter * minor * added isArrayOfHashes * renamed constants * added error package * added transactionObject * fixed IotaUnitConverter From 937a2b2e38a95d8b07fadf3affffb44e4b180cae Mon Sep 17 00:00:00 2001 From: davassi Date: Sun, 18 Dec 2016 12:33:26 +0100 Subject: [PATCH 06/12] Implemented broadcastAndStore and sendTrytes --- src/main/java/jota/IotaAPIProxy.java | 72 +++++++- .../java/jota/error/ArgumentException.java | 5 +- src/main/java/jota/error/BaseException.java | 25 +-- .../jota/error/NotEnoughBalanceException.java | 5 +- src/main/java/jota/model/Bundle.java | 162 ++++++++++++++++++ src/main/java/jota/model/Input.java | 48 ++++++ src/main/java/jota/pow/Curl.java | 46 ++--- src/main/java/jota/utils/Constants.java | 1 - src/main/java/jota/utils/Converter.java | 25 ++- src/main/java/jota/utils/IotaAPIUtils.java | 119 ++++++++++++- .../java/jota/utils/IotaUnitConverter.java | 3 +- src/main/java/jota/utils/IotaUnits.java | 1 + .../java/jota/utils/SeedRandomGenerator.java | 4 +- src/main/java/jota/utils/Signing.java | 26 ++- src/main/java/jota/utils/TrytesConverter.java | 15 +- src/test/java/jota/SendMessageTest.java | 12 ++ src/test/java/jota/TrytesConverterTest.java | 12 ++ teststore.txt | 1 + 18 files changed, 514 insertions(+), 68 deletions(-) create mode 100644 src/main/java/jota/model/Bundle.java create mode 100644 src/main/java/jota/model/Input.java create mode 100644 src/test/java/jota/SendMessageTest.java create mode 100644 teststore.txt diff --git a/src/main/java/jota/IotaAPIProxy.java b/src/main/java/jota/IotaAPIProxy.java index 5ce6806..8901c71 100644 --- a/src/main/java/jota/IotaAPIProxy.java +++ b/src/main/java/jota/IotaAPIProxy.java @@ -2,6 +2,8 @@ package jota; import jota.dto.request.*; import jota.dto.response.*; +import jota.model.Transaction; +import jota.utils.Converter; import jota.utils.IotaAPIUtils; import okhttp3.OkHttpClient; import org.slf4j.Logger; @@ -15,10 +17,12 @@ import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Properties; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * IotaAPIProxy Builder. Usage: @@ -193,10 +197,6 @@ public class IotaAPIProxy { // end of proxied calls. - public GetBundleResponse getBundle(String transaction) { - return IotaAPIUtils.getBundle(transaction); - } - /** * Generates a new address from a seed and returns the remainderAddress. * This is either done deterministically, or by providing the index of the new remainderAddress @@ -240,8 +240,72 @@ public class IotaAPIProxy { return GetNewAddressResponse.create(allAddresses); } + /* + * newAddress + * broadcastAndStore + * sendTrytes + * + getTransactionsObjects + findTransactionObjects + getLatestInclusion + getInputs + prepareTransfers + sendTransfer + replayBundle + broadcastBundle + getBundle + getTransfers + getAccountData + */ + /** + * + * @param trytes + * @return a StoreTransactionsResponse + */ + public StoreTransactionsResponse broadcastAndStore(final String ... trytes) { + + try { + broadcastTransactions(trytes); + } catch (Exception e) { + log.error("Impossible to broadcastAndStore, aborting.", e); + throw new IllegalStateException("BroadcastAndStore Illegal state Exception"); + } + return storeTransactions(trytes); + + } + + /** + * Facade method: Gets transactions to approve, attaches to Tangle, broadcasts and stores + * @param {array} trytes + * @param {int} depth + * @param {int} minWeightMagnitude + * @return + */ + public List sendTrytes(final String trytes, final int minWeightMagnitude) { + + final GetTransactionsToApproveResponse txs = getTransactionsToApprove(minWeightMagnitude); + + // attach to tangle - do pow + final GetAttachToTangleResponse res = attachToTangle(txs.getTrunkTransaction(), txs.getBranchTransactionToApprove(), minWeightMagnitude, trytes); + + try { + broadcastAndStore(res.getTrytes()); + } catch (Exception e) { + log.error("Impossible to sendTrytes, aborting.", e); + throw new IllegalStateException("sendTrytes Illegal state Exception"); + } + + return Arrays.stream(res.getTrytes()) + .map(Converter::transactionObject) + .collect(Collectors.toList()); + } + public GetBundleResponse getBundle(String transaction) { + return null; //IotaAPIUtils.getBundle(transaction); + } + + public static class Builder { String protocol, host, port; diff --git a/src/main/java/jota/error/ArgumentException.java b/src/main/java/jota/error/ArgumentException.java index 759b694..c3da421 100644 --- a/src/main/java/jota/error/ArgumentException.java +++ b/src/main/java/jota/error/ArgumentException.java @@ -4,7 +4,10 @@ package jota.error; * Created by Adrian on 09.12.2016. */ public class ArgumentException extends BaseException { + + private static final long serialVersionUID = -7850044681919575720L; + public ArgumentException() { - super("wrong arguments passed to function"); + super("Wrong arguments passed to function"); } } diff --git a/src/main/java/jota/error/BaseException.java b/src/main/java/jota/error/BaseException.java index b1eb210..2931408 100644 --- a/src/main/java/jota/error/BaseException.java +++ b/src/main/java/jota/error/BaseException.java @@ -1,31 +1,29 @@ package jota.error; -import org.apache.commons.lang3.StringUtils; - +import java.util.Arrays; import java.util.Collection; /** * Created by Adrian on 09.12.2016. */ public class BaseException extends Exception { + + private static final long serialVersionUID = 5617085097507773343L; + protected Collection messages; public BaseException(String msg) { super(msg); } - public BaseException(String msg, Exception cause) { super(msg, cause); } - public BaseException(Collection messages) { - super(); this.messages = messages; } - public BaseException(Collection messages, Exception cause) { super(cause); this.messages = messages; @@ -33,19 +31,6 @@ public class BaseException extends Exception { @Override public String getMessage() { - String msg; - - if (this.messages != null && !this.messages.isEmpty()) { - msg = "["; - - for (String message : this.messages) { - msg += message + ","; - } - - msg = StringUtils.removeEnd(msg, ",") + "]"; - - } else msg = super.getMessage(); - - return msg; + return Arrays.toString(messages.toArray()); } } diff --git a/src/main/java/jota/error/NotEnoughBalanceException.java b/src/main/java/jota/error/NotEnoughBalanceException.java index c7099d6..64c516c 100644 --- a/src/main/java/jota/error/NotEnoughBalanceException.java +++ b/src/main/java/jota/error/NotEnoughBalanceException.java @@ -4,7 +4,10 @@ package jota.error; * Created by Adrian on 09.12.2016. */ public class NotEnoughBalanceException extends BaseException { + + private static final long serialVersionUID = -3807270816402226476L; + public NotEnoughBalanceException() { - super("not enough balance dude"); + super("Not enough balance"); } } diff --git a/src/main/java/jota/model/Bundle.java b/src/main/java/jota/model/Bundle.java new file mode 100644 index 0000000..675fbd7 --- /dev/null +++ b/src/main/java/jota/model/Bundle.java @@ -0,0 +1,162 @@ +package jota.model; + +import jota.pow.Curl; +import jota.utils.Constants; +import jota.utils.Converter; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by pinpong on 09.12.16. + */ +public class Bundle { + + private List transactions; + private int length; + + public static String EMPTY_HASH = "999999999999999999999999999999999999999999999999999999999999999999999999999999999"; + + + public Bundle() { + this(new ArrayList(), 0); + } + + public Bundle(List transactions, int length) { + this.transactions = transactions; + this.length = length; + } + + public List getTransactions() { + return transactions; + } + + public void setTransactions(List transactions) { + this.transactions = transactions; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public void addEntry(int signatureMessageLength, String slice, long value, String tag, long timestamp) { + for (int i = 0; i < signatureMessageLength; i++) { + //TODO + +/* var transactionObject = new Object(); + transactionObject.address = address; + transactionObject.value = i == 0 ? value : 0; + transactionObject.tag = tag; + transactionObject.timestamp = timestamp; + + this.bundle[this.bundle.length] = transactionObject; +*/ + } + } + + public void finalize() { + + Curl curl = new Curl(); + curl.reset(); + + for (int i = 0; i < this.getTransactions().size(); i++) { + + int[] valueTrits = Converter.trits(this.getTransactions().get(i).getValue()); + while (valueTrits.length < 81) { + valueTrits[valueTrits.length] = 0; + } + + int[] timestampTrits = Converter.trits(this.getTransactions().get(i).getTimestamp()); + while (timestampTrits.length < 27) { + timestampTrits[timestampTrits.length] = 0; + } + + int[] currentIndexTrits = Converter.trits(this.getTransactions().get(i).setCurrentIndex("" + i)); + while (currentIndexTrits.length < 27) { + currentIndexTrits[currentIndexTrits.length] = 0; + } + + int[] lastIndexTrits = Converter.trits(this.getTransactions().get(i).setLastIndex("" + (this.getTransactions().size() - 1))); + while (lastIndexTrits.length < 27) { + lastIndexTrits[lastIndexTrits.length] = 0; + } + int[] t = Converter.trits(this.getTransactions().get(i).getAddress() + Converter.trytes(valueTrits) + this.getTransactions().get(i).getTag() + Converter.trytes(timestampTrits) + Converter.trytes(currentIndexTrits) + Converter.trytes(lastIndexTrits)); + curl.absorb(t, 0, t.length); + } + + int[] hash = new int[90]; + curl.squeeze(hash, 0, hash.length); + String hashInTrytes = Converter.trytes(hash); + + for (int i = 0; i < this.getTransactions().size(); i++) { + this.getTransactions().get(i).setBundle(hashInTrytes); + } + } + + + public void addTrytes(List signatureFragments) { + String emptySignatureFragment = ""; + String emptyHash = EMPTY_HASH; + + for (int j = 0; emptySignatureFragment.length() < 2187; j++) { + emptySignatureFragment += '9'; + } + + for (int i = 0; i < this.getTransactions().size(); i++) { + + // Fill empty signatureMessageFragment + this.getTransactions().get(i).setSignatureFragments(signatureFragments.get(i) == null ? signatureFragments.get(i) : emptySignatureFragment); + // Fill empty trunkTransaction + this.getTransactions().get(i).setTrunkTransaction(emptyHash); + + // Fill empty branchTransaction + this.getTransactions().get(i).setBranchTransaction(emptyHash); + + // Fill empty nonce + this.getTransactions().get(i).setNonce(emptyHash); + } + } + + public int[] normalizedBundle(String bundleHash) { + int[] normalizedBundle = new int[33 * 27 + 27]; + + for (int i = 0; i < 3; i++) { + + long sum = 0; + for (int j = 0; j < 27; j++) { + + sum += (normalizedBundle[i * 27 + j] = Converter.value(Converter.trits("" + bundleHash.charAt(i * 27 + j)))); + } + + if (sum >= 0) { + while (sum-- > 0) { + for (int j = 0; j < 27; j++) { + if (normalizedBundle[i * 27 + j] > -13) { + normalizedBundle[i * 27 + j]--; + break; + } + } + } + } else { + + while (sum++ < 0) { + + for (int j = 0; j < 27; j++) { + + if (normalizedBundle[i * 27 + j] < 13) { + normalizedBundle[i * 27 + j]++; + break; + } + } + } + } + } + + return normalizedBundle; + } + +} diff --git a/src/main/java/jota/model/Input.java b/src/main/java/jota/model/Input.java new file mode 100644 index 0000000..16ce445 --- /dev/null +++ b/src/main/java/jota/model/Input.java @@ -0,0 +1,48 @@ +package jota.model; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * Created by Adrian on 09.12.2016. + */ +public class Input { + private String address; + private long balance; + private int keyIndex; + + public Input(String address, long balance, int keyIndex) { + this.address = address; + this.balance = balance; + this.keyIndex = keyIndex; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public long getBalance() { + return balance; + } + + public void setBalance(long balance) { + this.balance = balance; + } + + public int getKeyIndex() { + return keyIndex; + } + + public void setKeyIndex(int keyIndex) { + this.keyIndex = keyIndex; + } +} \ No newline at end of file diff --git a/src/main/java/jota/pow/Curl.java b/src/main/java/jota/pow/Curl.java index d72ed45..764a54a 100644 --- a/src/main/java/jota/pow/Curl.java +++ b/src/main/java/jota/pow/Curl.java @@ -15,15 +15,37 @@ public class Curl { private int[] state = new int[STATE_LENGTH]; - public void absorb(final int[] trits, int offset, int length) { + public Curl 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); + + return this; } + public Curl 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]; + } + } + return this; + } + + public Curl reset() { + for (int stateIndex = 0; stateIndex < STATE_LENGTH; stateIndex++) { + state[stateIndex] = 0; + } + return this; + } + public int[] squeeze(final int[] trits, int offset, int length) { do { @@ -35,26 +57,10 @@ public class Curl { return state; } - public 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; } - public void setState(int[] state) { this.state = state; } + public void setState(int[] state) { + this.state = state; + } } diff --git a/src/main/java/jota/utils/Constants.java b/src/main/java/jota/utils/Constants.java index 2b9d4a5..5f8f9a2 100644 --- a/src/main/java/jota/utils/Constants.java +++ b/src/main/java/jota/utils/Constants.java @@ -11,5 +11,4 @@ public class Constants { public static int ADDRESS_LENGTH_WITHOUT_CHECKSUM = 81; public static int ADDRESS_LENGTH_WITH_CHECKSUM = 90; - } diff --git a/src/main/java/jota/utils/Converter.java b/src/main/java/jota/utils/Converter.java index 0a94107..90550e7 100644 --- a/src/main/java/jota/utils/Converter.java +++ b/src/main/java/jota/utils/Converter.java @@ -4,8 +4,15 @@ import jota.model.Transaction; import jota.pow.Curl; import java.util.Arrays; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Converter { + + private static final Logger log = LoggerFactory.getLogger(Converter.class); private static final int RADIX = 3; private static final int MAX_TRIT_VALUE = (RADIX - 1) / 2, MIN_TRIT_VALUE = -MAX_TRIT_VALUE; @@ -146,20 +153,26 @@ public class Converter { } } } - - public static Transaction transactionObject(String trytes) { - if (trytes == null) return null; - + + public static Transaction transactionObject(final String trytes) { + + if (StringUtils.isEmpty(trytes)) { + log.warn("Warning: empty trytes in input for transactionObject"); + return null; + } + // validity check for (int i = 2279; i < 2295; i++) { if (trytes.charAt(i) != '9') { + log.warn("Trytes {} does not seem a valid tryte", trytes); return null; } } + int[] transactionTrits = Converter.trits(trytes); int[] hash = new int[90]; - Curl curl = new Curl(); + final Curl curl = new Curl(); // we need a fluent Curl. // generate the correct transaction hash curl.reset(); @@ -183,4 +196,4 @@ public class Converter { return trx; } -} \ No newline at end of file +} diff --git a/src/main/java/jota/utils/IotaAPIUtils.java b/src/main/java/jota/utils/IotaAPIUtils.java index 3da5c2d..ee759cd 100644 --- a/src/main/java/jota/utils/IotaAPIUtils.java +++ b/src/main/java/jota/utils/IotaAPIUtils.java @@ -1,10 +1,17 @@ package jota.utils; -import jota.dto.response.GetBundleResponse; -import org.apache.commons.lang3.NotImplementedException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jota.model.Bundle; +import jota.model.Input; +import jota.model.Transaction; + /** * Client Side computation service * @@ -36,8 +43,112 @@ public class IotaAPIUtils { return address; } - public static GetBundleResponse getBundle(final String transaction) { - throw new NotImplementedException("Not yet implemented"); + public static String transactionTrytes(Transaction trx) { + int[] valueTrits = Converter.trits(trx.getValue()); + while (valueTrits.length < 81) { + valueTrits[valueTrits.length] = 0; + } + + int[] timestampTrits = Converter.trits(trx.getTimestamp()); + while (timestampTrits.length < 27) { + timestampTrits[timestampTrits.length] = 0; + } + + int[] currentIndexTrits = Converter.trits(trx.getTimestamp()); + while (currentIndexTrits.length < 27) { + currentIndexTrits[currentIndexTrits.length] = 0; + } + + int[] lastIndexTrits = Converter.trits(trx.getCurrentIndex()); + while (lastIndexTrits.length < 27) { + lastIndexTrits[lastIndexTrits.length] = 0; + } + + return trx.getSignatureFragments() + + trx.getAddress() + + Converter.trytes(valueTrits) + + trx.getTag() + + Converter.trytes(timestampTrits) + + Converter.trytes(currentIndexTrits) + + Converter.trytes(lastIndexTrits) + + trx.getBundle() + + trx.getTrunkTransaction() + + trx.getBranchTransaction() + + trx.getNonce(); + } + + public static List signInputsAndReturn(String seed, List inputs, Bundle bundle, + List signatureFragments) { + bundle.finalize(); + bundle.addTrytes(signatureFragments); + + // SIGNING OF INPUTS + // + // Here we do the actual signing of the inputs + // Iterate over all bundle transactions, find the inputs + // Get the corresponding private key and calculate the signatureFragment + for (int i = 0; i < bundle.getTransactions().size(); i++) { + if (Long.parseLong(bundle.getTransactions().get(i).getValue()) < 0) { + String thisAddress = bundle.getTransactions().get(i).getAddress(); + + // Get the corresponding keyIndex of the address + int keyIndex = 0; + for (int k = 0; k < inputs.size(); k++) { + if (inputs.get(k).getAddress().equals(thisAddress)) { + keyIndex = inputs.get(k).getKeyIndex(); + break; + } + } + + String bundleHash = bundle.getTransactions().get(i).getBundle(); + + // Get corresponding private key of address + int[] key = Signing.key(Converter.trits(seed), keyIndex, 2); + + // First 6561 trits for the firstFragment + int[] firstFragment = Arrays.copyOfRange(key, 0, 6561); + + // Get the normalized bundle hash + int[] normalizedBundleHash = bundle.normalizedBundle(bundleHash); + + // First bundle fragment uses 27 trytes + int[] firstBundleFragment = Arrays.copyOfRange(normalizedBundleHash, 0, 27); + + // Calculate the new signatureFragment with the first bundle fragment + int[] firstSignedFragment = Signing.signatureFragment(firstBundleFragment, firstFragment); + + // Convert signature to trytes and assign the new signatureFragment + bundle.getTransactions().get(i).setSignatureFragments(Converter.trytes(firstSignedFragment)); + + // Because the signature is > 2187 trytes, we need to + // find the second transaction to add the remainder of the signature + for (int j = 0; j < bundle.getTransactions().size(); j++) { + // Same address as well as value = 0 (as we already spent the input) + if (bundle.getTransactions().get(j).getAddress() == thisAddress && Long.parseLong(bundle.getTransactions().get(j).getValue()) == 0) { + // Use the second 6562 trits + int[] secondFragment = Arrays.copyOfRange(key, 6561, 6561 * 2); + + // The second 27 to 54 trytes of the bundle hash + int[] secondBundleFragment = Arrays.copyOfRange(normalizedBundleHash, 27, 27 * 2); + + // Calculate the new signature + int[] secondSignedFragment = Signing.signatureFragment(secondBundleFragment, secondFragment); + + // Convert signature to trytes and assign it again to this bundle entry + bundle.getTransactions().get(j).setSignatureFragments(Converter.trytes(secondSignedFragment)); + } + } + } + } + + List bundleTrytes = new ArrayList<>(); + + // Convert all bundle entries into trytes + for (Transaction tx : bundle.getTransactions()) { + bundleTrytes.add(IotaAPIUtils.transactionTrytes(tx)); + } + Collections.reverse(bundleTrytes); + return bundleTrytes; } } diff --git a/src/main/java/jota/utils/IotaUnitConverter.java b/src/main/java/jota/utils/IotaUnitConverter.java index cdd4529..d25a39e 100644 --- a/src/main/java/jota/utils/IotaUnitConverter.java +++ b/src/main/java/jota/utils/IotaUnitConverter.java @@ -43,8 +43,9 @@ public class IotaUnitConverter { public static IotaUnits findOptimalIotaUnitToDisplay(long amount) { int length = String.valueOf(amount).length(); - if (amount < 0) // do not count "-" sign + if (amount < 0) {// do not count "-" sign length -= 1; + } IotaUnits units = IotaUnits.IOTA; diff --git a/src/main/java/jota/utils/IotaUnits.java b/src/main/java/jota/utils/IotaUnits.java index 28e52f3..779dde0 100644 --- a/src/main/java/jota/utils/IotaUnits.java +++ b/src/main/java/jota/utils/IotaUnits.java @@ -8,6 +8,7 @@ package jota.utils; * Table of IOTA units based off of the standard system of Units **/ public enum IotaUnits { + IOTA("i", 0), KILO_IOTA("Ki", 3), MEGA_IOTA("Mi", 6), diff --git a/src/main/java/jota/utils/SeedRandomGenerator.java b/src/main/java/jota/utils/SeedRandomGenerator.java index 8e702cb..fa8b58c 100644 --- a/src/main/java/jota/utils/SeedRandomGenerator.java +++ b/src/main/java/jota/utils/SeedRandomGenerator.java @@ -1,6 +1,6 @@ package jota.utils; -import java.util.Random; +import java.security.SecureRandom; /** * Created by pinpong on 13.12.16. @@ -10,7 +10,7 @@ public class SeedRandomGenerator { public static String generateNewSeed() { char[] chars = Constants.TRYTE_ALPHABET.toCharArray(); StringBuilder builder = new StringBuilder(); - Random random = new Random(); + SecureRandom random = new SecureRandom(); for (int i = 0; i < Constants.SEED_LENGTH_MAX; i++) { char c = chars[random.nextInt(chars.length)]; builder.append(c); diff --git a/src/main/java/jota/utils/Signing.java b/src/main/java/jota/utils/Signing.java index a5e2553..f04339f 100644 --- a/src/main/java/jota/utils/Signing.java +++ b/src/main/java/jota/utils/Signing.java @@ -34,7 +34,6 @@ public class Signing { while (length-- > 0) { for (int i = 0; i < 27; i++) { - curl.squeeze(buffer, offset, buffer.length); for (int j = 0; j < 243; j++) { key.add(buffer[j]); @@ -81,6 +80,31 @@ public class Signing { } return digests; } + + public static int[] signatureFragment(int[] normalizedBundleFragment, int[] keyFragment) { + + int[] signatureFragment = keyFragment; + int[] hash; + + Curl curl = new Curl(); + + for (int i = 0; i < 27; i++) { + + hash = Arrays.copyOfRange(signatureFragment, i * 243, (i + 1) * 243); + + for (int j = 0; j < 13 - normalizedBundleFragment[i]; j++) { + curl.reset() + .absorb(hash, 0, hash.length) + .squeeze(hash, 0, hash.length); + } + + for (int j = 0; j < 243; j++) { + signatureFragment[i * 243 + j] = hash[j]; + } + } + + return signatureFragment; + } public static int[] address(int[] digests) { final Curl curl = new Curl(); diff --git a/src/main/java/jota/utils/TrytesConverter.java b/src/main/java/jota/utils/TrytesConverter.java index 5c5d843..ab9d02a 100644 --- a/src/main/java/jota/utils/TrytesConverter.java +++ b/src/main/java/jota/utils/TrytesConverter.java @@ -4,7 +4,7 @@ package jota.utils; * Created by pinpong on 01.12.16. */ public class TrytesConverter { - + /** * Conversion of ascii encoded bytes to trytes. * Input is a string (can be stringified JSON object), return value is Trytes @@ -19,7 +19,9 @@ public class TrytesConverter { * b. The second value is the remainder (decimal value - first value), divided by 27 * 3. The two values returned from Step 2. are then input as indices into the available values list ('9ABCDEFGHIJKLMNOPQRSTUVWXYZ') to get the correct tryte value *

- * EXAMPLES + * + * EXAMPLE + * * Lets say we want to convert the ASCII character "Z". * 1. 'Z' has a decimal value of 90. * 2. 90 can be represented as 9 + 3 * 27. To make it simpler: @@ -30,10 +32,11 @@ public class TrytesConverter { * b. The second tryte value is '9ABCDEFGHIJKLMNOPQRSTUVWXYZ'[3] === "C" * Our tryte pair is "IC" *

- * RESULT: - * The ASCII char "Z" is represented as "IC" in trytes. + * + * @param inputString + * @return + * The ASCII char "Z" is represented as "IC" in trytes. */ - public static String toTrytes(String inputString) { StringBuilder trytes = new StringBuilder(); @@ -67,7 +70,6 @@ public class TrytesConverter { * Last character = } * Everything after that is 9's padding */ - public static String toString(String inputTrytes) { StringBuilder string = new StringBuilder(); @@ -81,7 +83,6 @@ public class TrytesConverter { int decimalValue = firstValue + secondValue * 27; String character = Character.toString((char) decimalValue); - string.append(character); } diff --git a/src/test/java/jota/SendMessageTest.java b/src/test/java/jota/SendMessageTest.java new file mode 100644 index 0000000..9a62c04 --- /dev/null +++ b/src/test/java/jota/SendMessageTest.java @@ -0,0 +1,12 @@ +package jota; + +import org.junit.Test; + +public class SendMessageTest { + + @Test + public void sendMessage() { + + } + +} diff --git a/src/test/java/jota/TrytesConverterTest.java b/src/test/java/jota/TrytesConverterTest.java index 7741cff..28b54a6 100644 --- a/src/test/java/jota/TrytesConverterTest.java +++ b/src/test/java/jota/TrytesConverterTest.java @@ -3,6 +3,9 @@ package jota; import jota.utils.TrytesConverter; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.apache.commons.lang3.RandomStringUtils; /** * Created by pinpong on 01.12.16. @@ -17,5 +20,14 @@ public class TrytesConverterTest { public void shouldConvertTrytesToString() { assertEquals(TrytesConverter.toString("IC"), "Z"); } + + @Test + public void shouldConvertBackAndForth() { + String str = RandomStringUtils.randomAlphabetic(1000).toUpperCase(); + System.err.println(str); + String back = TrytesConverter.toString(TrytesConverter.toTrytes(str)); + + assertTrue(str.equals(back)); + } } diff --git a/teststore.txt b/teststore.txt new file mode 100644 index 0000000..833978f --- /dev/null +++ b/teststore.txt @@ -0,0 +1 @@ +curl http://localhost:14265 -X POST -H Content-Type: application/json -d {'command': 'storeTransactions', 'trytes': ['GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCCOYOXYPCLR9PBEY9ORZIEPPDNTI9CQWYZUOTAVBXPSBOFEQAPFLWXSWUIUSJMSJIIIZWIKIRH9GCOEVZFKNXEVCUCIIWZQCQEUVRZOCMEL9AMGXJNMLJCIA9UWGRPPHCEOPTSVPKPPPCMQXYBHMSODTWUOABPKWFFFQJHCBVYXLHEWPD9YUDFTGNCYAKQKVEZYRBQRBXIAUX9SVEDUKGMTWQIYXRGSWYRK9SRONVGTW9YGHSZRIXWGPCCUCDRMAXBPDFVHSRYWHGB9DQSQFQKSNICGPIPTRZINYRXQAFSWSEWIFRMSBMGTNYPRWFSOIIWWT9IDSELM9JUOOWFNCCSHUSMGNROBFJX9JQ9XT9PKEGQYQAWAFPRVRRVQPUQBHLSNTEFCDKBWRCDX9EYOBB9KPMTLNNQLADBDLZPRVBCKVCYQEOLARJYAGTBFR9QLPKZBOYWZQOVKCVYRGYI9ZEFIQRKYXLJBZJDBJDJVQZCGYQMROVHNDBLGNLQODPUXFNTADDVYNZJUVPGB9LVPJIYLAPBOEHPMRWUIAJXVQOEM9ROEYUOTNLXVVQEYRQWDTQGDLEYFIYNDPRAIXOZEBCS9P99AZTQQLKEILEVXMSHBIDHLXKUOMMNFKPYHONKEYDCHMUNTTNRYVMMEYHPGASPZXASKRUPWQSHDMU9VPS99ZZ9SJJYFUJFFMFORBYDILBXCAVJDPDFHTTTIYOVGLRDYRTKHXJORJVYRPTDH9ZCPZ9ZADXZFRSFPIQKWLBRNTWJHXTOAUOL9FVGTUMMPYGYICJDXMOESEVDJWLMCVTJLPIEKBE9JTHDQWV9MRMEWFLPWGJFLUXI9BXPSVWCMUWLZSEWHBDZKXOLYNOZAPOYLQVZAQMOHGTTQEUAOVKVRRGAHNGPUEKHFVPVCOYSJAWHZU9DRROHBETBAFTATVAUGOEGCAYUXACLSSHHVYDHMDGJP9AUCLWLNTFEVGQGHQXSKEMVOVSKQEEWHWZUDTYOBGCURRZSJZLFVQQAAYQO9TRLFFN9HTDQXBSPPJYXMNGLLBHOMNVXNOWEIDMJVCLLDFHBDONQJCJVLBLCSMDOUQCKKCQJMGTSTHBXPXAMLMSXRIPUBMBAWBFNLHLUJTRJLDERLZFUBUSMF999XNHLEEXEENQJNOFFPNPQ9PQICHSATPLZVMVIWLRTKYPIXNFGYWOJSQDAXGFHKZPFLPXQEHCYEAGTIWIJEZTAVLNUMAFWGGLXMBNUQTOFCNLJTCDMWVVZGVBSEBCPFSM99FLOIDTCLUGPSEDLOKZUAEVBLWNMODGZBWOVQT9DPFOTSKRABQAVOQ9RXWBMAKFYNDCZOJGTCIDMQSQQSODKDXTPFLNOKSIZEOY9HFUTLQRXQMEPGOXQGLLPNSXAUCYPGZMNWMQWSWCKAQYKXJTWINSGPPZG9HLDLEAWUWEVCTVRCBDFOXKUROXH9HXXAXVPEJFRSLOGRVGYZASTEBAQNXJJROCYRTDPYFUIQJVDHAKEG9YACV9HCPJUEUKOYFNWDXCCJBIFQKYOXGRDHVTHEQUMHO999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999RKWEEVD99A99999999A99999999NFDPEEZCWVYLKZGSLCQNOFUSENIXRHWWTZFBXMPSQHEDFWZULBZFEOMNLRNIDQKDNNIELAOXOVMYEI9PGTKORV9IKTJZQUBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSKUCUEMD9M9SQJ999999TKORV9IKTJZQUBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSKUCUEMD9M9SQJ999999999999999999999999999999999999999999999999999999999999999999999999999999999999999']} From bbe837975e31bb2eb1e3907318a4601f906e751b Mon Sep 17 00:00:00 2001 From: davassi Date: Mon, 19 Dec 2016 19:03:11 +0100 Subject: [PATCH 07/12] Implemented prepareTransfer and getInputs --- src/main/java/jota/IotaAPIProxy.java | 371 ++++++++++++++++-- .../GetBalancesAndFormatResponse.java | 34 ++ .../dto/response/GetNewAddressResponse.java | 2 +- src/main/java/jota/model/Transfer.java | 63 ++- src/main/java/jota/pow/Curl.java | 8 + src/main/java/jota/utils/InputValidator.java | 47 +++ src/main/java/jota/utils/IotaAPIUtils.java | 6 +- src/main/java/jota/utils/Signing.java | 118 ++++-- src/test/java/jota/IotaAPIProxyTest.java | 2 +- 9 files changed, 576 insertions(+), 75 deletions(-) create mode 100644 src/main/java/jota/dto/response/GetBalancesAndFormatResponse.java diff --git a/src/main/java/jota/IotaAPIProxy.java b/src/main/java/jota/IotaAPIProxy.java index 8901c71..6bb6032 100644 --- a/src/main/java/jota/IotaAPIProxy.java +++ b/src/main/java/jota/IotaAPIProxy.java @@ -1,28 +1,60 @@ package jota; -import jota.dto.request.*; -import jota.dto.response.*; -import jota.model.Transaction; -import jota.utils.Converter; -import jota.utils.IotaAPIUtils; -import okhttp3.OkHttpClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import retrofit2.Call; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jota.dto.request.IotaAttachToTangleRequest; +import jota.dto.request.IotaBroadcastTransactionRequest; +import jota.dto.request.IotaCommandRequest; +import jota.dto.request.IotaFindTransactionsRequest; +import jota.dto.request.IotaGetBalancesRequest; +import jota.dto.request.IotaGetInclusionStateRequest; +import jota.dto.request.IotaGetTransactionsToApproveRequest; +import jota.dto.request.IotaGetTrytesRequest; +import jota.dto.request.IotaNeighborsRequest; +import jota.dto.request.IotaStoreTransactionsRequest; +import jota.dto.response.AddNeighborsResponse; +import jota.dto.response.BroadcastTransactionsResponse; +import jota.dto.response.FindTransactionResponse; +import jota.dto.response.GetAttachToTangleResponse; +import jota.dto.response.GetBalancesAndFormatResponse; +import jota.dto.response.GetBalancesResponse; +import jota.dto.response.GetBundleResponse; +import jota.dto.response.GetInclusionStateResponse; +import jota.dto.response.GetNeighborsResponse; +import jota.dto.response.GetNewAddressResponse; +import jota.dto.response.GetNodeInfoResponse; +import jota.dto.response.GetTipsResponse; +import jota.dto.response.GetTransactionsToApproveResponse; +import jota.dto.response.GetTrytesResponse; +import jota.dto.response.InterruptAttachingToTangleResponse; +import jota.dto.response.RemoveNeighborsResponse; +import jota.dto.response.StoreTransactionsResponse; +import jota.model.Bundle; +import jota.model.Input; +import jota.model.Transaction; +import jota.model.Transfer; +import jota.utils.Converter; +import jota.utils.InputValidator; +import jota.utils.IotaAPIUtils; +import okhttp3.OkHttpClient; +import retrofit2.Call; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; /** * IotaAPIProxy Builder. Usage: @@ -174,6 +206,10 @@ public class IotaAPIProxy { final Call res = service.getBalances(IotaGetBalancesRequest.createIotaGetBalancesRequest(threshold, addresses)); return wrapCheckedException(res).body(); } + + public GetBalancesResponse getBalances(Integer threshold, List addresses) { + return getBalances(threshold, addresses.toArray(new String[] {})); + } public InterruptAttachingToTangleResponse interruptAttachingToTangle() { final Call res = service.interruptAttachingToTangle(IotaCommandRequest.createInterruptAttachToTangleRequest()); @@ -244,18 +280,20 @@ public class IotaAPIProxy { * newAddress * broadcastAndStore * sendTrytes - * - getTransactionsObjects - findTransactionObjects - getLatestInclusion - getInputs - prepareTransfers - sendTransfer - replayBundle - broadcastBundle - getBundle - getTransfers - getAccountData + * prepareTransfers + * getInputs + + getTransfers + sendTransfer + getBundle + + getTransactionsObjects + findTransactionObjects + getLatestInclusion + + replayBundle + broadcastBundle + getAccountData */ /** @@ -272,7 +310,6 @@ public class IotaAPIProxy { throw new IllegalStateException("BroadcastAndStore Illegal state Exception"); } return storeTransactions(trytes); - } /** @@ -296,16 +333,288 @@ public class IotaAPIProxy { throw new IllegalStateException("sendTrytes Illegal state Exception"); } - return Arrays.stream(res.getTrytes()) - .map(Converter::transactionObject) - .collect(Collectors.toList()); + //return Arrays.stream(res.getTrytes()).map(Converter::transactionObject).collect(Collectors.toList()); + final List trx = new ArrayList<>(); + + for (final String tx : Arrays.asList(res.getTrytes())) { + trx.add(Converter.transactionObject(tx)); + } + return trx; + } + + public List findAndGetTxs(final String addresses) { + + final FindTransactionResponse res = findTransactionsByAddresses(addresses); + return getTransactionsObjects(res.getHashes()); + } + + /** + * Wrapper function for getTrytes and transactionObjects + * gets the trytes and transaction object from a list of transaction hashes + * + * @method getTransactionsObjects + * @param {array} hashes + * @return + * @returns {function} callback + * @returns {object} success + **/ + public List getTransactionsObjects(String ... hashes) { + + if (!InputValidator.isArrayOfHashes(hashes)) { + throw new IllegalStateException("Not an Array of Hashes: " + Arrays.toString(hashes)); + } + + final GetTrytesResponse trytesResponse = getTrytes(hashes); + + final List trxs = new ArrayList<>(); + + for (final String tryte : trytesResponse.getTrytes()) { + trxs.add(Converter.transactionObject(tryte)); + } + return trxs; } + /** + * Prepares transfer by generating bundle, finding and signing inputs + * + * @method prepareTransfers + * @param {string} seed + * @param {object} transfers + * @param {object} options + * @property {array} inputs Inputs used for signing. Needs to have correct keyIndex and address value + * @property {string} address Remainder address + * @param {function} callback + * @return + * @returns {array} trytes Returns bundle trytes + **/ + public List prepareTransfers(final String seed, final List transfers, String remainder, List inputs) { + + // Input validation of transfers object + if (!InputValidator.isTransfersCollectionCorrect(transfers)) { + throw new IllegalStateException("Invalid Transfer"); + } + + // Create a new bundle + final Bundle bundle = new Bundle(); + final List signatureFragments = new ArrayList<>(); + + int totalValue = 0; + String tag; + + // Iterate over all transfers, get totalValue + // and prepare the signatureFragments, message and tag + for (final Transfer transfer : transfers) { + + int signatureMessageLength = 1; + + // If message longer than 2187 trytes, increase signatureMessageLength (add 2nd transaction) + if (transfer.getMessage().length() > 2187) { + + // Get total length, message / maxLength (2187 trytes) + signatureMessageLength += Math.floor(transfer.getMessage().length() / 2187); + + String msgCopy = new String(transfer.getMessage()); + + // While there is still a message, copy it + while (!msgCopy.isEmpty()) { + + String fragment = StringUtils.substring(msgCopy, 0, 2187); + msgCopy = StringUtils.substring(msgCopy, 2187, msgCopy.length()); + + // Pad remainder of fragment + for (int j = 0; fragment.length() < 2187; j++) { + fragment += "9"; + } + + signatureFragments.add(fragment); + } + } else { + // Else, get single fragment with 2187 of 9's trytes + String fragment = StringUtils.substring(transfer.getMessage(), 0, 2187); + + for (int j = 0; fragment.length() < 2187; j++) { + fragment += '9'; + } + + signatureFragments.add(fragment); + } + + // get current timestamp in seconds + long timestamp = (long) Math.floor(Calendar.getInstance().getTimeInMillis() / 1000); + + // If no tag defined, get 27 tryte tag. + tag = transfer.getTag().isEmpty() ? "999999999999999999999999999" : transfer.getTag(); + + // Pad for required 27 tryte length + for (int j = 0; tag.length() < 27; j++) { + tag += '9'; + } + + // Add first entry to the bundle + bundle.addEntry(signatureMessageLength, transfer.getAddress(), transfer.getValue(), tag, timestamp); + // Sum up total value + totalValue += transfer.getValue(); + } + + // Get inputs if we are sending tokens + if (totalValue != 0) { + + // Case 1: user provided inputs + // Validate the inputs by calling getBalances + if (!inputs.isEmpty()) { + + // Get list if addresses of the provided inputs + List inputsAddresses = new ArrayList<>(); + for (final Input i : inputs) { + inputsAddresses.add(i.getAddress()); + } + + GetBalancesResponse resbalances = getBalances(100, inputsAddresses); + String[] balances = resbalances.getBalances(); + + + List confirmedInputs = new ArrayList<>(); + int totalBalance = 0; int i = 0; + for (String balance : balances) { + long thisBalance = Integer.parseInt(balance); + totalBalance += thisBalance; + + // If input has balance, add it to confirmedInputs + if (thisBalance > 0) { + Input inputEl = inputs.get(i++); + inputEl.setBalance(thisBalance); + confirmedInputs.add(inputEl); + } + } + + // Return not enough balance error + if (totalValue > totalBalance) { + throw new IllegalStateException("Not enough balance"); + } + + return IotaAPIUtils.signInputsAndReturn(seed, confirmedInputs, bundle, signatureFragments); + } + + // Case 2: Get inputs deterministically + // + // If no inputs provided, derive the addresses from the seed and + // confirm that the inputs exceed the threshold + else { + + GetBalancesAndFormatResponse newinputs = getInputs(seed, Collections.EMPTY_LIST, 0, 0, totalValue); + // If inputs with enough balance + return IotaAPIUtils.signInputsAndReturn(seed, newinputs.getInput(), bundle, signatureFragments); + } + } else { + + // If no input required, don't sign and simply finalize the bundle + bundle.finalize(); + bundle.addTrytes(signatureFragments); + + List trxb = bundle.getTransactions(); + List bundleTrytes = new ArrayList<>(); + for (Transaction tx : trxb) { + jota.utils.IotaAPIUtils.transactionTrytes(tx); + } + Collections.reverse(bundleTrytes); + return bundleTrytes; + } + } + + /** + * Gets the inputs of a seed + * + * @method getInputs + * @param {string} seed + * @param {object} options + * @property {int} start Starting key index + * @property {int} end Ending key index + * @property {int} threshold Min balance required + * @param {function} callback + **/ + public GetBalancesAndFormatResponse getInputs(final String seed, final List balances, int start, int end, int threshold) { + + // validate the seed + if (!InputValidator.isTrytes(seed, 0)) { + throw new IllegalStateException("Invalid Seed"); + } + + // If start value bigger than end, return error + // or if difference between end and start is bigger than 500 keys + if (start > end || end > (start + 500)) { + throw new IllegalStateException("Invalid inputs provided"); + } + + // Case 1: start and end + // + // If start and end is defined by the user, simply iterate through the keys + // and call getBalances + if (end != 0) { + + List allAddresses = new ArrayList<>(); + + for (int i = start; i < end; i++) { + + String address = IotaAPIUtils.newAddress(seed, i, false); + allAddresses.add(address); + } + + return getBalanceAndFormat(allAddresses, balances, threshold, start, end); + } + // Case 2: iterate till threshold || end + // + // Either start from index: 0 or start (if defined) until threshold is reached. + // Calls getNewAddress and deterministically generates and returns all addresses + // We then do getBalance, format the output and return it + else { + + final GetNewAddressResponse res = getNewAddress(seed, start, false, 0, true); + return getBalanceAndFormat(res.getAddresses(), balances, threshold, start, end); + } + } + + // Calls getBalances and formats the output + // returns the final inputsObject then + public GetBalancesAndFormatResponse getBalanceAndFormat(final List addresses, + final List balances, long threshold, int start, int end) { + + GetBalancesResponse bres = getBalances(100, addresses); + + // If threshold defined, keep track of whether reached or not + // else set default to true + boolean thresholdReached = threshold != 0 ? false : true; int i = -1; + + List inputs = new ArrayList<>(); + long totalBalance = 0; + + for (String address : addresses) { + + long balance = Long.parseLong(balances.get(++i)); + + if (balance > 0) { + final Input newEntry = new Input(address, balance, start+i); + + inputs.add(newEntry); + // Increase totalBalance of all aggregated inputs + totalBalance += balance; + + if (thresholdReached == false && totalBalance >= threshold) { + thresholdReached = true; + break; + } + } + } + + if (thresholdReached) { + return GetBalancesAndFormatResponse.create(inputs, totalBalance); + } + throw new IllegalStateException("Not enough balance"); + } + public GetBundleResponse getBundle(String transaction) { return null; //IotaAPIUtils.getBundle(transaction); } - public static class Builder { String protocol, host, port; diff --git a/src/main/java/jota/dto/response/GetBalancesAndFormatResponse.java b/src/main/java/jota/dto/response/GetBalancesAndFormatResponse.java new file mode 100644 index 0000000..493d063 --- /dev/null +++ b/src/main/java/jota/dto/response/GetBalancesAndFormatResponse.java @@ -0,0 +1,34 @@ +package jota.dto.response; + +import java.util.List; + +import jota.model.Input; + +public class GetBalancesAndFormatResponse extends AbstractResponse { + + private List input; + private long totalBalance; + + public List getInput() { + return input; + } + + public void setInput(List input) { + this.input = input; + } + + public long getTotalBalance() { + return totalBalance; + } + + public void setTotalBalance(long totalBalance) { + this.totalBalance = totalBalance; + } + + public static GetBalancesAndFormatResponse create(List inputs, long totalBalance2) { + GetBalancesAndFormatResponse res = new GetBalancesAndFormatResponse(); + res.setInput(inputs); + res.setTotalBalance(totalBalance2); + return res; + } +} diff --git a/src/main/java/jota/dto/response/GetNewAddressResponse.java b/src/main/java/jota/dto/response/GetNewAddressResponse.java index b54384d..484e17e 100644 --- a/src/main/java/jota/dto/response/GetNewAddressResponse.java +++ b/src/main/java/jota/dto/response/GetNewAddressResponse.java @@ -12,7 +12,7 @@ public class GetNewAddressResponse extends AbstractResponse { return res; } - public List getAddress() { + public List getAddresses() { return addresses; } } diff --git a/src/main/java/jota/model/Transfer.java b/src/main/java/jota/model/Transfer.java index fa149ee..a1b675e 100644 --- a/src/main/java/jota/model/Transfer.java +++ b/src/main/java/jota/model/Transfer.java @@ -1,7 +1,6 @@ package jota.model; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; +import com.google.gson.Gson; /** * Created by pinpong on 02.12.16. @@ -11,21 +10,33 @@ public class Transfer { private String timestamp; private String address; private String hash; - private Integer persistence; + private Boolean persistence; private long value; + private String message; + private String tag; - public Transfer(String timestamp, String address, String hash, Integer persistence, long value) { - + public Transfer(String timestamp, String address, String hash, Boolean persistence, long value, String message, + String tag) { this.timestamp = timestamp; this.address = address; this.hash = hash; this.persistence = persistence; this.value = value; + this.message = message; + this.tag = tag; + + } + + public Transfer(String address, long value, String message, String tag) { + this.address = address; + this.value = value; + this.message = message; + this.tag = tag; } @Override public String toString() { - return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); + return new Gson().toJson(this); } public String getAddress() { @@ -36,7 +47,7 @@ public class Transfer { return hash; } - public Integer getPersistence() { + public Boolean getPersistence() { return persistence; } @@ -47,4 +58,40 @@ public class Transfer { public long getValue() { return value; } -} + + public String getMessage() { + return message; + } + + public String getTag() { + return tag; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public void setAddress(String address) { + this.address = address; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public void setPersistence(Boolean persistence) { + this.persistence = persistence; + } + + public void setValue(long value) { + this.value = value; + } + + public void setMessage(String message) { + this.message = message; + } + + public void setTag(String tag) { + this.tag = tag; + } +} \ No newline at end of file diff --git a/src/main/java/jota/pow/Curl.java b/src/main/java/jota/pow/Curl.java index 764a54a..b804250 100644 --- a/src/main/java/jota/pow/Curl.java +++ b/src/main/java/jota/pow/Curl.java @@ -25,6 +25,10 @@ public class Curl { return this; } + + public Curl absorb(final int[] trits) { + return absorb(trits, 0, trits.length); + } public Curl transform() { @@ -56,6 +60,10 @@ public class Curl { return state; } + + public int[] squeeze(final int[] trits) { + return squeeze(trits, 0, trits.length); + } public int[] getState() { return state; diff --git a/src/main/java/jota/utils/InputValidator.java b/src/main/java/jota/utils/InputValidator.java index d658bd3..6e85443 100644 --- a/src/main/java/jota/utils/InputValidator.java +++ b/src/main/java/jota/utils/InputValidator.java @@ -1,5 +1,12 @@ package jota.utils; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import jota.model.Transaction; +import jota.model.Transfer; + /** * Created by pinpong on 02.12.16. */ @@ -20,6 +27,10 @@ public class InputValidator { public static boolean isTrytes(final String trytes, final int length) { return trytes.matches("^[A-Z9]{" + (length == 0 ? "0," : length) + "}$"); } + + public static boolean isValue(final String value) { + return StringUtils.isNumeric(value); + } public static boolean isArrayOfHashes(String[] hashes) { if (hashes == null) return false; @@ -38,4 +49,40 @@ public class InputValidator { } return true; } + + /** + * checks if input is correct hash collections + * + * @method isTransfersArray + * @param {array} hash + * @returns {boolean} + **/ + public static boolean isTransfersCollectionCorrect(final List transfers) { + + for (final Transfer transfer : transfers) { + if (!isTransfersArray(transfer)) { + return false; + } + } + return true; + } + + public static boolean isTransfersArray(final Transfer transfer) { + + if (!isAddress(transfer.getAddress())) { + return false; + } + + // Check if message is correct trytes of any length + if (!isTrytes(transfer.getMessage(), 0)) { + return false; + } + + // Check if tag is correct trytes of {0,27} trytes + if (!isTrytes(transfer.getTag(), 27)) { + return false; + } + + return true; + } } diff --git a/src/main/java/jota/utils/IotaAPIUtils.java b/src/main/java/jota/utils/IotaAPIUtils.java index ee759cd..617f014 100644 --- a/src/main/java/jota/utils/IotaAPIUtils.java +++ b/src/main/java/jota/utils/IotaAPIUtils.java @@ -77,8 +77,10 @@ public class IotaAPIUtils { + trx.getNonce(); } - public static List signInputsAndReturn(String seed, List inputs, Bundle bundle, - List signatureFragments) { + public static List signInputsAndReturn(final String seed, + final List inputs, + final Bundle bundle, + final List signatureFragments) { bundle.finalize(); bundle.addTrytes(signatureFragments); diff --git a/src/main/java/jota/utils/Signing.java b/src/main/java/jota/utils/Signing.java index f04339f..e0f2e84 100644 --- a/src/main/java/jota/utils/Signing.java +++ b/src/main/java/jota/utils/Signing.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import jota.model.Bundle; import jota.pow.Curl; public class Signing { @@ -52,35 +53,6 @@ public class Signing { return a; } - public static int[] digests(int[] key) { - final Curl curl = new Curl(); - - int[] digests = new int[(int) Math.floor(key.length / 6561) * 243]; - int[] buffer = new int[243]; - - for (int i = 0; i < Math.floor(key.length / 6561); i++) { - int[] keyFragment = Arrays.copyOfRange(key, i * 6561, (i + 1) * 6561); - - for (int j = 0; j < 27; j++) { - - buffer = Arrays.copyOfRange(keyFragment, j * 243, (j + 1) * 243); - for (int k = 0; k < 26; k++) { - curl.reset(); - curl.absorb(buffer, 0, buffer.length); - curl.squeeze(buffer, 0, buffer.length); - } - System.arraycopy(buffer, 0, keyFragment, j * 243, 243); - } - - curl.reset(); - curl.absorb(keyFragment, 0, keyFragment.length); - curl.squeeze(buffer, 0, buffer.length); - - System.arraycopy(buffer, 0, digests, i * 243, 243); - } - return digests; - } - public static int[] signatureFragment(int[] normalizedBundleFragment, int[] keyFragment) { int[] signatureFragment = keyFragment; @@ -109,9 +81,91 @@ public class Signing { public static int[] address(int[] digests) { final Curl curl = new Curl(); int[] address = new int[243]; - curl.reset(); - curl.absorb(digests, 0, digests.length); - curl.squeeze(address, 0, address.length); + curl.reset() + .absorb(digests) + .squeeze(address); return address; } + + public static int[] digests(int[] key) { + final Curl curl = new Curl(); + + int[] digests = new int[(int) Math.floor(key.length / 6561) * 243]; + int[] buffer = new int[243]; + + for (int i = 0; i < Math.floor(key.length / 6561); i++) { + int[] keyFragment = Arrays.copyOfRange(key, i * 6561, (i + 1) * 6561); + + for (int j = 0; j < 27; j++) { + + buffer = Arrays.copyOfRange(keyFragment, j * 243, (j + 1) * 243); + for (int k = 0; k < 26; k++) { + curl.reset() + .absorb(buffer) + .squeeze(buffer); + } + System.arraycopy(buffer, 0, keyFragment, j * 243, 243); + } + + curl.reset(); + curl.absorb(keyFragment, 0, keyFragment.length); + curl.squeeze(buffer, 0, buffer.length); + + System.arraycopy(buffer, 0, digests, i * 243, 243); + } + return digests; + } + + public static int[] digest(int[] normalizedBundleFragment, int[] signatureFragment) { + + int[] buffer = new int[243]; + + Curl curl = new Curl().reset(); + + for (int i = 0; i < 27; i++) { + buffer = Arrays.copyOfRange(signatureFragment, i * 243, (i + 1) * 243); + + for (int j = normalizedBundleFragment[i] + 13; j-- > 0; ) { + + new Curl().reset() + .absorb(buffer) + .squeeze(buffer); + } + curl.absorb(buffer); + } + curl.squeeze(buffer); + + return buffer; + } + + public static Boolean validateSignatures(String expectedAddress, String[] signatureFragments, String bundleHash) { + + Bundle bundle = new Bundle(); + + int[][] normalizedBundleFragments = new int[3][27]; + int[] normalizedBundleHash = bundle.normalizedBundle(bundleHash); + + // Split hash into 3 fragments + for (int i = 0; i < 3; i++) { + normalizedBundleFragments[i] = Arrays.copyOfRange(normalizedBundleHash, i * 27, (i + 1) * 27); + } + + // Get digests + int[] digests = new int[signatureFragments.length * 243 + 243]; + + for (int i = 0; i < signatureFragments.length; i++) { + + int[] digestBuffer = digest(normalizedBundleFragments[i % 3], Converter.trits(signatureFragments[i])); + + for (int j = 0; j < 243; j++) { + + digests[i * 243 + j] = digestBuffer[j]; + } + } + + String address = Converter.trytes(address(digests)); + + return (expectedAddress.equals(address)); + } } + diff --git a/src/test/java/jota/IotaAPIProxyTest.java b/src/test/java/jota/IotaAPIProxyTest.java index 9b16cda..7f62a73 100644 --- a/src/test/java/jota/IotaAPIProxyTest.java +++ b/src/test/java/jota/IotaAPIProxyTest.java @@ -129,6 +129,6 @@ public class IotaAPIProxyTest { @Test public void shouldCreateANewAddress() { final GetNewAddressResponse res = proxy.getNewAddress(TEST_SEED, 0, false, 1, false); - assertThat(res.getAddress(), Is.is(Collections.singletonList(TEST_ADDRESS_WITHOUT_CHECKSUM))); + assertThat(res.getAddresses(), Is.is(Collections.singletonList(TEST_ADDRESS_WITHOUT_CHECKSUM))); } } \ No newline at end of file From bee2aa330260b193e19cb6efc2f63069ca259f0d Mon Sep 17 00:00:00 2001 From: Oliver Nitzschke Date: Thu, 22 Dec 2016 10:19:21 +0100 Subject: [PATCH 08/12] fixed getTransactionsToApprove (#12) * updated GetNodeInfo * fixed getTransactionsToApprove * fixed getTransactionsToApprove --- src/main/java/jota/IotaAPIProxy.java | 2 +- .../java/jota/dto/response/GetNodeInfoResponse.java | 5 +++++ .../response/GetTransactionsToApproveResponse.java | 12 ++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/jota/IotaAPIProxy.java b/src/main/java/jota/IotaAPIProxy.java index 6bb6032..7ec6373 100644 --- a/src/main/java/jota/IotaAPIProxy.java +++ b/src/main/java/jota/IotaAPIProxy.java @@ -324,7 +324,7 @@ public class IotaAPIProxy { final GetTransactionsToApproveResponse txs = getTransactionsToApprove(minWeightMagnitude); // attach to tangle - do pow - final GetAttachToTangleResponse res = attachToTangle(txs.getTrunkTransaction(), txs.getBranchTransactionToApprove(), minWeightMagnitude, trytes); + final GetAttachToTangleResponse res = attachToTangle(txs.getTrunkTransaction(), txs.getBranchTransaction(), minWeightMagnitude, trytes); try { broadcastAndStore(res.getTrytes()); diff --git a/src/main/java/jota/dto/response/GetNodeInfoResponse.java b/src/main/java/jota/dto/response/GetNodeInfoResponse.java index f7477e4..233c0fb 100644 --- a/src/main/java/jota/dto/response/GetNodeInfoResponse.java +++ b/src/main/java/jota/dto/response/GetNodeInfoResponse.java @@ -4,6 +4,7 @@ public class GetNodeInfoResponse extends AbstractResponse { private String appName; private String appVersion; + private String jreVersion; private int jreAvailableProcessors; private long jreFreeMemory; private long jreMaxMemory; @@ -26,6 +27,10 @@ public class GetNodeInfoResponse extends AbstractResponse { return appVersion; } + public String getJreVersion() { + return jreVersion; + } + public Integer getJreAvailableProcessors() { return jreAvailableProcessors; } diff --git a/src/main/java/jota/dto/response/GetTransactionsToApproveResponse.java b/src/main/java/jota/dto/response/GetTransactionsToApproveResponse.java index 5f76cf3..ba7d478 100644 --- a/src/main/java/jota/dto/response/GetTransactionsToApproveResponse.java +++ b/src/main/java/jota/dto/response/GetTransactionsToApproveResponse.java @@ -3,13 +3,13 @@ package jota.dto.response; public class GetTransactionsToApproveResponse extends AbstractResponse { private String trunkTransaction; - private String branchTransactionToApprove; - - public String getBranchTransactionToApprove() { - return branchTransactionToApprove; - } + private String branchTransaction; public String getTrunkTransaction() { return trunkTransaction; } -} + + public String getBranchTransaction() { + return branchTransaction; + } +} \ No newline at end of file From 9af3d2d0277059e8dc29b63888220a39eb2e7b4e Mon Sep 17 00:00:00 2001 From: Oliver Nitzschke Date: Thu, 22 Dec 2016 14:55:13 +0100 Subject: [PATCH 09/12] updated tests (#13) * updated tests * Bundle: fixed addEntry --- src/main/java/jota/IotaAPIProxy.java | 58 +++++------------------ src/main/java/jota/model/Bundle.java | 16 +++---- src/main/java/jota/model/Transaction.java | 8 ++++ src/test/java/jota/IotaAPIProxyTest.java | 54 +++++++++++++++++---- 4 files changed, 73 insertions(+), 63 deletions(-) diff --git a/src/main/java/jota/IotaAPIProxy.java b/src/main/java/jota/IotaAPIProxy.java index 7ec6373..1f9de61 100644 --- a/src/main/java/jota/IotaAPIProxy.java +++ b/src/main/java/jota/IotaAPIProxy.java @@ -1,48 +1,7 @@ package jota; -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Properties; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jota.dto.request.IotaAttachToTangleRequest; -import jota.dto.request.IotaBroadcastTransactionRequest; -import jota.dto.request.IotaCommandRequest; -import jota.dto.request.IotaFindTransactionsRequest; -import jota.dto.request.IotaGetBalancesRequest; -import jota.dto.request.IotaGetInclusionStateRequest; -import jota.dto.request.IotaGetTransactionsToApproveRequest; -import jota.dto.request.IotaGetTrytesRequest; -import jota.dto.request.IotaNeighborsRequest; -import jota.dto.request.IotaStoreTransactionsRequest; -import jota.dto.response.AddNeighborsResponse; -import jota.dto.response.BroadcastTransactionsResponse; -import jota.dto.response.FindTransactionResponse; -import jota.dto.response.GetAttachToTangleResponse; -import jota.dto.response.GetBalancesAndFormatResponse; -import jota.dto.response.GetBalancesResponse; -import jota.dto.response.GetBundleResponse; -import jota.dto.response.GetInclusionStateResponse; -import jota.dto.response.GetNeighborsResponse; -import jota.dto.response.GetNewAddressResponse; -import jota.dto.response.GetNodeInfoResponse; -import jota.dto.response.GetTipsResponse; -import jota.dto.response.GetTransactionsToApproveResponse; -import jota.dto.response.GetTrytesResponse; -import jota.dto.response.InterruptAttachingToTangleResponse; -import jota.dto.response.RemoveNeighborsResponse; -import jota.dto.response.StoreTransactionsResponse; +import jota.dto.request.*; +import jota.dto.response.*; import jota.model.Bundle; import jota.model.Input; import jota.model.Transaction; @@ -51,11 +10,20 @@ import jota.utils.Converter; import jota.utils.InputValidator; import jota.utils.IotaAPIUtils; import okhttp3.OkHttpClient; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import retrofit2.Call; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeUnit; + /** * IotaAPIProxy Builder. Usage: * @@ -413,7 +381,7 @@ public class IotaAPIProxy { // Get total length, message / maxLength (2187 trytes) signatureMessageLength += Math.floor(transfer.getMessage().length() / 2187); - String msgCopy = new String(transfer.getMessage()); + String msgCopy = transfer.getMessage(); // While there is still a message, copy it while (!msgCopy.isEmpty()) { @@ -598,7 +566,7 @@ public class IotaAPIProxy { // Increase totalBalance of all aggregated inputs totalBalance += balance; - if (thresholdReached == false && totalBalance >= threshold) { + if (!thresholdReached && totalBalance >= threshold) { thresholdReached = true; break; } diff --git a/src/main/java/jota/model/Bundle.java b/src/main/java/jota/model/Bundle.java index 675fbd7..90d966d 100644 --- a/src/main/java/jota/model/Bundle.java +++ b/src/main/java/jota/model/Bundle.java @@ -1,7 +1,6 @@ package jota.model; import jota.pow.Curl; -import jota.utils.Constants; import jota.utils.Converter; import java.util.ArrayList; @@ -43,18 +42,14 @@ public class Bundle { this.length = length; } - public void addEntry(int signatureMessageLength, String slice, long value, String tag, long timestamp) { + public void addEntry(int signatureMessageLength, String address, long value, String tag, long timestamp) { for (int i = 0; i < signatureMessageLength; i++) { - //TODO -/* var transactionObject = new Object(); - transactionObject.address = address; - transactionObject.value = i == 0 ? value : 0; - transactionObject.tag = tag; - transactionObject.timestamp = timestamp; + List transactions = new ArrayList<>(getTransactions()); + transactions.add(new Transaction(address, String.valueOf(i == 0 ? value : 0), tag, String.valueOf(timestamp))); + + setTransactions(transactions); - this.bundle[this.bundle.length] = transactionObject; -*/ } } @@ -84,6 +79,7 @@ public class Bundle { while (lastIndexTrits.length < 27) { lastIndexTrits[lastIndexTrits.length] = 0; } + int[] t = Converter.trits(this.getTransactions().get(i).getAddress() + Converter.trytes(valueTrits) + this.getTransactions().get(i).getTag() + Converter.trytes(timestampTrits) + Converter.trytes(currentIndexTrits) + Converter.trytes(lastIndexTrits)); curl.absorb(t, 0, t.length); } diff --git a/src/main/java/jota/model/Transaction.java b/src/main/java/jota/model/Transaction.java index 8a74f5a..0f7f962 100644 --- a/src/main/java/jota/model/Transaction.java +++ b/src/main/java/jota/model/Transaction.java @@ -41,6 +41,14 @@ public class Transaction { this.nonce = nonce; } + + public Transaction(String address, String value, String tag, String timestamp) { + this.address = address; + this.value = value; + this.tag = tag; + this.timestamp = timestamp; + } + @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); diff --git a/src/test/java/jota/IotaAPIProxyTest.java b/src/test/java/jota/IotaAPIProxyTest.java index 7f62a73..43b4c94 100644 --- a/src/test/java/jota/IotaAPIProxyTest.java +++ b/src/test/java/jota/IotaAPIProxyTest.java @@ -3,12 +3,15 @@ package jota; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import jota.dto.response.*; +import jota.model.Transfer; import org.hamcrest.core.Is; import org.hamcrest.core.IsNull; import org.junit.Before; import org.junit.Test; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import static org.junit.Assert.assertThat; @@ -28,6 +31,9 @@ public class IotaAPIProxyTest { private static final String TEST_TRYTES = "BYSWEAUTWXHXZ9YBZISEK9LUHWGMHXCGEVNZHRLUWQFCUSDXZHOFHWHL9MQPVJXXZLIXPXPXF9KYEREFSKCPKYIIKPZVLHUTDFQKKVVBBN9ATTLPCNPJDWDEVIYYLGPZGCWXOBDXMLJC9VO9QXTTBLAXTTBFUAROYEGQIVB9MJWJKXJMCUPTWAUGFZBTZCSJVRBGMYXTVBDDS9MYUJCPZ9YDWWQNIPUAIJXXSNLKUBSCOIJPCLEFPOXFJREXQCUVUMKSDOVQGGHRNILCO9GNCLWFM9APMNMWYASHXQAYBEXF9QRIHIBHYEJOYHRQJAOKAQ9AJJFQ9WEIWIJOTZATIBOXQLBMIJU9PCGBLVDDVFP9CFFSXTDUXMEGOOFXWRTLFGV9XXMYWEMGQEEEDBTIJ9OJOXFAPFQXCDAXOUDMLVYRMRLUDBETOLRJQAEDDLNVIRQJUBZBO9CCFDHIX9MSQCWYAXJVWHCUPTRSXJDESISQPRKZAFKFRULCGVRSBLVFOPEYLEE99JD9SEBALQINPDAZHFAB9RNBH9AZWIJOTLBZVIEJIAYGMC9AZGNFWGRSWAXTYSXVROVNKCOQQIWGPNQZKHUNODGYADPYLZZZUQRTJRTODOUKAOITNOMWNGHJBBA99QUMBHRENGBHTH9KHUAOXBVIVDVYYZMSEYSJWIOGGXZVRGN999EEGQMCOYVJQRIRROMPCQBLDYIGQO9AMORPYFSSUGACOJXGAQSPDY9YWRRPESNXXBDQ9OZOXVIOMLGTSWAMKMTDRSPGJKGBXQIVNRJRFRYEZ9VJDLHIKPSKMYC9YEGHFDS9SGVDHRIXBEMLFIINOHVPXIFAZCJKBHVMQZEVWCOSNWQRDYWVAIBLSCBGESJUIBWZECPUCAYAWMTQKRMCHONIPKJYYTEGZCJYCT9ABRWTJLRQXKMWY9GWZMHYZNWPXULNZAPVQLPMYQZCYNEPOCGOHBJUZLZDPIXVHLDMQYJUUBEDXXPXFLNRGIPWBRNQQZJSGSJTTYHIGGFAWJVXWL9THTPWOOHTNQWCNYOYZXALHAZXVMIZE9WMQUDCHDJMIBWKTYH9AC9AFOT9DPCADCV9ZWUTE9QNOMSZPTZDJLJZCJGHXUNBJFUBJWQUEZDMHXGBPTNSPZBR9TGSKVOHMOQSWPGFLSWNESFKSAZY9HHERAXALZCABFYPOVLAHMIHVDBGKUMDXC9WHHTIRYHZVWNXSVQUWCR9M9RAGMFEZZKZ9XEOQGOSLFQCHHOKLDSA9QCMDGCGMRYJZLBVIFOLBIJPROKMHOYTBTJIWUZWJMCTKCJKKTR9LCVYPVJI9AHGI9JOWMIWZAGMLDFJA9WU9QAMEFGABIBEZNNAL9OXSBFLOEHKDGHWFQSHMPLYFCNXAAZYJLMQDEYRGL9QKCEUEJ9LLVUOINVSZZQHCIKPAGMT9CAYIIMTTBCPKWTYHOJIIY9GYNPAJNUJ9BKYYXSV9JSPEXYMCFAIKTGNRSQGUNIYZCRT9FOWENSZQPD9ALUPYYAVICHVYELYFPUYDTWUSWNIYFXPX9MICCCOOZIWRNJIDALWGWRATGLJXNAYTNIZWQ9YTVDBOFZRKO9CFWRPAQQRXTPACOWCPRLYRYSJARRKSQPR9TCFXDVIXLP9XVL99ERRDSOHBFJDJQQGGGCZNDQ9NYCTQJWVZIAELCRBJJFDMCNZU9FIZRPGNURTXOCDSQGXTQHKHUECGWFUUYS9J9NYQ9U9P9UUP9YMZHWWWCIASCFLCMSKTELZWUGCDE9YOKVOVKTAYPHDF9ZCCQAYPJIJNGSHUIHHCOSSOOBUDOKE9CJZGYSSGNCQJVBEFTZFJ9SQUHOASKRRGBSHWKBCBWBTJHOGQ9WOMQFHWJVEG9NYX9KWBTCAIXNXHEBDIOFO9ALYMFGRICLCKKLG9FOBOX9PDWNQRGHBKHGKKRLWTBEQMCWQRLHAVYYZDIIPKVQTHYTWQMTOACXZOQCDTJTBAAUWXSGJF9PNQIJ9AJRUMUVCPWYVYVARKR9RKGOUHHNKNVGGPDDLGKPQNOYHNKAVVKCXWXOQPZNSLATUJT9AUWRMPPSWHSTTYDFAQDXOCYTZHOYYGAIM9CELMZ9AZPWB9MJXGHOKDNNSZVUDAGXTJJSSZCPZVPZBYNNTUQABSXQWZCHDQSLGK9UOHCFKBIBNETK999999999999999999999999999999999999999999999999999999999999999999999999999999999NOXDXXKUDWLOFJLIPQIBRBMGDYCPGDNLQOLQS99EQYKBIU9VHCJVIPFUYCQDNY9APGEVYLCENJIOBLWNB999999999XKBRHUD99C99999999NKZKEKWLDKMJCI9N9XQOLWEPAYWSH9999999999999999999999999KDDTGZLIPBNZKMLTOLOXQVNGLASESDQVPTXALEKRMIOHQLUHD9ELQDBQETS9QFGTYOYWLNTSKKMVJAUXSIROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999IROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999"; private static final String TEST_MILESTONE = "SMYMAKKPSUKCKDRUEYCGZJTYCZ9HHDMDUWBAPXARGURPQRHTAJDASRWMIDTPTBNDKDEFBUTBGGAFX9999"; private static final Integer TEST_MILESTONE_INDEX = 8059; + private static final String TEST_MESSAGE = "JOTA"; + private static final String TEST_TAG = "JOTASPAM9999999999999999999"; + private IotaAPIProxy proxy; @@ -40,6 +46,21 @@ public class IotaAPIProxyTest { public void shouldGetNodeInfo() { GetNodeInfoResponse nodeInfo = proxy.getNodeInfo(); assertThat(nodeInfo.getAppVersion(), IsNull.notNullValue()); + assertThat(nodeInfo.getAppName(), IsNull.notNullValue()); + //assertThat(nodeInfo.getJreVersion(), IsNull.notNullValue()); + assertThat(nodeInfo.getJreAvailableProcessors(), IsNull.notNullValue()); + assertThat(nodeInfo.getJreFreeMemory(), IsNull.notNullValue()); + assertThat(nodeInfo.getJreMaxMemory(), IsNull.notNullValue()); + assertThat(nodeInfo.getJreTotalMemory(), IsNull.notNullValue()); + assertThat(nodeInfo.getLatestMilestone(), IsNull.notNullValue()); + assertThat(nodeInfo.getLatestMilestoneIndex(), IsNull.notNullValue()); + assertThat(nodeInfo.getLatestSolidSubtangleMilestone(), IsNull.notNullValue()); + assertThat(nodeInfo.getLatestSolidSubtangleMilestoneIndex(), IsNull.notNullValue()); + assertThat(nodeInfo.getNeighbors(), IsNull.notNullValue()); + assertThat(nodeInfo.getPacketsQueueSize(), IsNull.notNullValue()); + assertThat(nodeInfo.getTime(), IsNull.notNullValue()); + assertThat(nodeInfo.getTips(), IsNull.notNullValue()); + assertThat(nodeInfo.getTransactionsToRequest(), IsNull.notNullValue()); } @Test @@ -70,25 +91,25 @@ public class IotaAPIProxyTest { public void shouldFindTransactionsByAddresses() { FindTransactionResponse trans = proxy.findTransactionsByAddresses(TEST_ADDRESS_WITH_CHECKSUM); System.err.println(gson.toJson(trans)); - assertThat(trans, IsNull.notNullValue()); + assertThat(trans.getHashes(), IsNull.notNullValue()); } @Test public void shouldFindTransactionsByApprovees() { FindTransactionResponse trans = proxy.findTransactionsByApprovees(new String[]{TEST_HASH}); - assertThat(trans, IsNull.notNullValue()); + assertThat(trans.getHashes(), IsNull.notNullValue()); } @Test public void shouldFindTransactionsByBundles() { FindTransactionResponse trans = proxy.findTransactionsByBundles(TEST_HASH); - assertThat(trans, IsNull.notNullValue()); + assertThat(trans.getHashes(), IsNull.notNullValue()); } @Test public void shouldFindTransactionsByDigests() { FindTransactionResponse trans = proxy.findTransactionsByDigests(TEST_HASH); - assertThat(trans, IsNull.notNullValue()); + assertThat(trans.getHashes(), IsNull.notNullValue()); } @@ -97,27 +118,32 @@ public class IotaAPIProxyTest { @Test public void shouldGetTrytes() { GetTrytesResponse res = proxy.getTrytes(TEST_HASH); - assertThat(res, IsNull.notNullValue()); + assertThat(res.getTrytes(), IsNull.notNullValue()); } @Test public void shouldGetInclusionStates() { GetInclusionStateResponse res = proxy.getInclusionStates(new String[]{TEST_ADDRESS_WITH_CHECKSUM}, new String[]{"DNSBRJWNOVUCQPILOQIFDKBFJMVOTGHLIMLLRXOHFTJZGRHJUEDAOWXQRYGDI9KHYFGYDWQJZKX999999"}); - assertThat(res, IsNull.notNullValue()); + assertThat(res.getStates(), IsNull.notNullValue()); } @Test // very long execution public void shouldGetTransactionsToApprove() { GetTransactionsToApproveResponse res = proxy.getTransactionsToApprove(27); - assertThat(res, IsNull.notNullValue()); + assertThat(res.getTrunkTransaction(), IsNull.notNullValue()); + assertThat(res.getBranchTransaction(), IsNull.notNullValue()); + } @Test public void shouldGetBalances() { GetBalancesResponse res = proxy.getBalances(100, new String[]{TEST_ADDRESS_WITH_CHECKSUM}); System.err.println(res); - assertThat(res, IsNull.notNullValue()); + assertThat(res.getBalances(), IsNull.notNullValue()); + assertThat(res.getMilestone(), IsNull.notNullValue()); + assertThat(res.getMilestoneIndex(), IsNull.notNullValue()); + } @Test @@ -131,4 +157,16 @@ public class IotaAPIProxyTest { final GetNewAddressResponse res = proxy.getNewAddress(TEST_SEED, 0, false, 1, false); assertThat(res.getAddresses(), Is.is(Collections.singletonList(TEST_ADDRESS_WITHOUT_CHECKSUM))); } + + @Test + public void shouldPrepareTransfer() { + List transfers = new ArrayList<>(); + transfers.add(new jota.model.Transfer(TEST_ADDRESS_WITH_CHECKSUM, 0, TEST_MESSAGE, TEST_TAG)); + proxy.prepareTransfers(TEST_SEED, transfers, null, null); + } + + @Test + public void shouldSendTrytes() { + proxy.sendTrytes(TEST_TRYTES, 18); + } } \ No newline at end of file From 96fed0bf788298db7e1d228b2451df6289a5747e Mon Sep 17 00:00:00 2001 From: pinpong Date: Thu, 22 Dec 2016 15:57:20 +0100 Subject: [PATCH 10/12] added getLatestInclusion --- src/main/java/jota/IotaAPIProxy.java | 18 ++++++++++++++---- src/test/java/jota/IotaAPIProxyTest.java | 6 ++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/java/jota/IotaAPIProxy.java b/src/main/java/jota/IotaAPIProxy.java index 1f9de61..0f8010f 100644 --- a/src/main/java/jota/IotaAPIProxy.java +++ b/src/main/java/jota/IotaAPIProxy.java @@ -250,15 +250,15 @@ public class IotaAPIProxy { * sendTrytes * prepareTransfers * getInputs - + * getLatestInclusion + getTransfers sendTransfer getBundle getTransactionsObjects findTransactionObjects - getLatestInclusion - + replayBundle broadcastBundle getAccountData @@ -582,7 +582,17 @@ public class IotaAPIProxy { public GetBundleResponse getBundle(String transaction) { return null; //IotaAPIUtils.getBundle(transaction); } - + + + public GetInclusionStateResponse getLatestInclusion(String[] hashes) { + GetNodeInfoResponse getNodeInfoResponse = getNodeInfo(); + if (getNodeInfoResponse == null) return null; + + String[] latestMilestone = {getNodeInfoResponse.getLatestSolidSubtangleMilestone()}; + + return getInclusionStates(hashes, latestMilestone); + } + public static class Builder { String protocol, host, port; diff --git a/src/test/java/jota/IotaAPIProxyTest.java b/src/test/java/jota/IotaAPIProxyTest.java index 43b4c94..bdea1b1 100644 --- a/src/test/java/jota/IotaAPIProxyTest.java +++ b/src/test/java/jota/IotaAPIProxyTest.java @@ -169,4 +169,10 @@ public class IotaAPIProxyTest { public void shouldSendTrytes() { proxy.sendTrytes(TEST_TRYTES, 18); } + + @Test + public void shouldGetLastInclusionState() { + GetInclusionStateResponse res = proxy.getLatestInclusion(new String[]{TEST_HASH}); + assertThat(res.getStates(), IsNull.notNullValue()); + } } \ No newline at end of file From 6a7a015ec65487d2e48d5f39091a17e294368720 Mon Sep 17 00:00:00 2001 From: pinpong Date: Thu, 22 Dec 2016 18:47:41 +0100 Subject: [PATCH 11/12] added findTransactionObjects --- src/main/java/jota/IotaAPIProxy.java | 18 +++++++++++------- src/test/java/jota/IotaAPIProxyTest.java | 5 +++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/jota/IotaAPIProxy.java b/src/main/java/jota/IotaAPIProxy.java index 0f8010f..d920943 100644 --- a/src/main/java/jota/IotaAPIProxy.java +++ b/src/main/java/jota/IotaAPIProxy.java @@ -309,13 +309,7 @@ public class IotaAPIProxy { } return trx; } - - public List findAndGetTxs(final String addresses) { - final FindTransactionResponse res = findTransactionsByAddresses(addresses); - return getTransactionsObjects(res.getHashes()); - } - /** * Wrapper function for getTrytes and transactionObjects * gets the trytes and transaction object from a list of transaction hashes @@ -326,7 +320,7 @@ public class IotaAPIProxy { * @returns {function} callback * @returns {object} success **/ - public List getTransactionsObjects(String ... hashes) { + public List getTransactionsObjects(String[] hashes) { if (!InputValidator.isArrayOfHashes(hashes)) { throw new IllegalStateException("Not an Array of Hashes: " + Arrays.toString(hashes)); @@ -342,6 +336,16 @@ public class IotaAPIProxy { return trxs; } + public List findTransactionObjects(String[] input) { + FindTransactionResponse ftr = findTransactions(input, null, null, null); + if (ftr == null || ftr.getHashes() == null) + + return null; + + // get the transaction objects of the transactions + return getTransactionsObjects(ftr.getHashes()); + } + /** * Prepares transfer by generating bundle, finding and signing inputs * diff --git a/src/test/java/jota/IotaAPIProxyTest.java b/src/test/java/jota/IotaAPIProxyTest.java index bdea1b1..ffb52ac 100644 --- a/src/test/java/jota/IotaAPIProxyTest.java +++ b/src/test/java/jota/IotaAPIProxyTest.java @@ -175,4 +175,9 @@ public class IotaAPIProxyTest { GetInclusionStateResponse res = proxy.getLatestInclusion(new String[]{TEST_HASH}); assertThat(res.getStates(), IsNull.notNullValue()); } + + @Test + public void shouldFindTransactionObjects() { + assertThat(proxy.findTransactionObjects(new String[]{TEST_ADDRESS_WITH_CHECKSUM}), IsNull.notNullValue()); + } } \ No newline at end of file From 9dfa0e315c54897586af8e4c82a491f1c3839372 Mon Sep 17 00:00:00 2001 From: pinpong Date: Thu, 22 Dec 2016 19:34:31 +0100 Subject: [PATCH 12/12] added documentation --- src/main/java/jota/IotaAPIProxy.java | 29 ++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main/java/jota/IotaAPIProxy.java b/src/main/java/jota/IotaAPIProxy.java index d920943..7946d67 100644 --- a/src/main/java/jota/IotaAPIProxy.java +++ b/src/main/java/jota/IotaAPIProxy.java @@ -336,6 +336,16 @@ public class IotaAPIProxy { return trxs; } + /** + * Wrapper function for findTransactions, getTrytes and transactionObjects + * Returns the transactionObject of a transaction hash. The input can be a valid + * findTransactions input + * + * @param {object} input + * @method getTransactionsObjects + * @returns {function} callback + * @returns {object} success + **/ public List findTransactionObjects(String[] input) { FindTransactionResponse ftr = findTransactions(input, null, null, null); if (ftr == null || ftr.getHashes() == null) @@ -582,12 +592,27 @@ public class IotaAPIProxy { } throw new IllegalStateException("Not enough balance"); } - + + /** + * Gets the associated bundle transactions of a single transaction + * Does validation of signatures, total sum as well as bundle order + * + * @method getBundle + * @param {string} transaction Hash of a tail transaction + * @returns {list} bundle Transaction objects + **/ public GetBundleResponse getBundle(String transaction) { return null; //IotaAPIUtils.getBundle(transaction); } - + /** + * Wrapper function for getNodeInfo and getInclusionStates + * + * @method getLatestInclusion + * @param {array} hashes + * @returns {function} callback + * @returns {array} state + **/ public GetInclusionStateResponse getLatestInclusion(String[] hashes) { GetNodeInfoResponse getNodeInfoResponse = getNodeInfo(); if (getNodeInfoResponse == null) return null;