diff --git a/README.md b/README.md index b42ccab6..96d311eb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![IOTA Logo][iota-logo] # IOTA Java Library - +[![Codacy Badge][codacy-badge]][codacy] [![JitPack][jitpack-badge]][jitpack] [![Build Status][travis-badge]][travis] [![License][license-badge]][license] @@ -36,7 +36,7 @@ All the boilerplate code for connecting to the node REST interface has been hidd 1. [Disclaimer](#disclaimer) 1. [Supporting the project](#supporting-the-project) 1. [Join the Discussion](#join-the-discussion) -1. [License](#license) +1. [License](codacity#license) ## Installation @@ -56,7 +56,7 @@ allprojects { Add this in your module `build.gradle` file: ```gradle dependencies { - compile 'com.github.iotaledger:iota-java:1.0.0-beta1' + compile 'com.github.iotaledger:iota-java:1.0.0-beta2' } ``` @@ -82,7 +82,7 @@ Add this in your module `pom.xml` file: ``` For the [VERSION_INFORMATION], you can choose to set it to a release number, or the first 10 characters of a commit hash. -`17e0de8ed5` or `1.0.0-beta1` +`f3200c8380` or `1.0.0-beta2` Or check it out at [Jitpack](https://jitpack.io/#iotaledger/iota-java) for more specific information @@ -96,7 +96,7 @@ Reference your new project to the "jota" project. ##### Linking jar In order to build the jar; the following command should be run on the "jota" project: -'mvn clean install -Prelease -DprofileIdEnabled=true' +'mvn clean install' This will generate a jar with the following format: `jota-[VERSION]-jar-with-dependencies.jar` @@ -279,17 +279,19 @@ See the License for the specific language governing permissions and limitations under the License. ``` -[iota-logo]: https://raw.githubusercontent.com/iotaledger/documentation/master/source/images/iota-logo.png +[iota-logo]: https://avatars0.githubusercontent.com/u/20126597?s=200&v=4 +[codacy]: https://www.codacy.com/app/kwek20/iota-java?utm_source=github.com&utm_medium=referral&utm_content=iotaledger/iota-java&utm_campaign=Badge_Grade +[codacy-badge]: https://api.codacy.com/project/badge/Grade/92feea51a15c4e589386c269475b8761 [jitpack]: https://jitpack.io/#iotaledger/iota-java [jitpack-badge]: https://jitpack.io/v/iotaledger/iota-java.svg [travis]: https://travis-ci.org/iotaledger/iota-java [travis-badge]: https://travis-ci.org/iotaledger/iota-java.svg?branch=master [license]: https://github.com/iotaledger/iota-java/blob/master/LICENSE [license-badge]: https://img.shields.io/github/license/iotaledger/iota-java.svg -[iota-iri]: https://github.com/iotaledger/iri/tree/v1.5.0 -[iota-iri-badge]: https://img.shields.io/badge/IOTA%20IRI%20compatibility-v1.5.0-blue.svg +[iota-iri]: https://github.com/iotaledger/iri/tree/v1.5.5 +[iota-iri-badge]: https://img.shields.io/badge/IOTA%20IRI%20compatibility-v1.5.5-blue.svg [iota-api]: https://iota.readme.io/reference -[iota-api-badge]: https://img.shields.io/badge/IOTA%20API%20coverage-14/15%20commands-red.svg +[iota-api-badge]: https://img.shields.io/badge/IOTA%20API%20coverage-15/15%20commands-green.svg [javadoc]: https://iotaledger.github.io/iota-java/javadoc/ [issues]: https://github.com/iotaledger/iota-java/issues [new-issue]: https://github.com/iotaledger/iota-java/issues/new diff --git a/jota/pom.xml b/jota/pom.xml index 80ee0e1d..94943293 100644 --- a/jota/pom.xml +++ b/jota/pom.xml @@ -5,7 +5,7 @@ org.iota jota-parent - 1.0.0-beta1 + 1.0.0-beta2 JOTA : Library diff --git a/jota/src/main/java/jota/IotaAPI.java b/jota/src/main/java/jota/IotaAPI.java index d7f4d1d7..263b787e 100644 --- a/jota/src/main/java/jota/IotaAPI.java +++ b/jota/src/main/java/jota/IotaAPI.java @@ -15,8 +15,6 @@ import java.util.*; import java.util.stream.Collectors; -import static jota.utils.Constants.*; - /** * IotaAPI Builder. Usage: @@ -29,7 +27,6 @@ * * {@code GetNodeInfoResponse response = api.getNodeInfo();} * - * @author davassi */ public class IotaAPI extends IotaAPICore { @@ -44,50 +41,148 @@ protected IotaAPI(Builder builder) { /** * 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. - * + *

+ * Deprecated -> Use the new functions {@link #getNextAvailableAddress}, {@link #getAddressesUnchecked} and {@link #generateNewAddresses} * @param seed Tryte-encoded seed. It should be noted that this seed is not transferred. * @param security Security level to be used for the private key / address. Can be 1, 2 or 3. * @param index Key index to start search from. If the index is provided, the generation of the address is not deterministic. * @param checksum Adds 9-tryte address checksum. * @param total Total number of addresses to generate. * @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. + * @return GetNewAddressResponse containing an array of strings with the specified number of addresses. * @throws ArgumentException is thrown when the specified input is not valid. */ + @Deprecated public GetNewAddressResponse getNewAddress(final String seed, int security, final int index, final boolean checksum, final int total, final boolean returnAll) throws ArgumentException { - StopWatch stopWatch = new StopWatch(); - - List allAddresses = new ArrayList<>(); - // If total number of addresses to generate is supplied, simply generate // and return the list of all addresses if (total != 0) { - for (int i = index; i < index + total; i++) { - allAddresses.add(IotaAPIUtils.newAddress(seed, security, i, checksum, customCurl.clone())); - } - return GetNewAddressResponse.create(allAddresses, stopWatch.getElapsedTimeMili()); + return getAddressesUnchecked(seed, security, checksum, index, total); } + // If !returnAll return only the last address that was generated + if (!returnAll) { + return generateNewAddresses(seed, security, checksum, 0, 1, true); + } else { + return generateNewAddresses(seed, security, checksum, 0, 1, false); + } + } + + /** + * Checks all addresses until the first unspent address is found. Starts at index 0. + * @param seed Tryte-encoded seed. It should be noted that this seed is not transferred. + * @param security Security level to be used for the private key / address. Can be 1, 2 or 3. + * @param checksum Adds 9-tryte address checksum. + * @return GetNewAddressResponse containing an array of strings with the specified number of addresses. + * @throws ArgumentException is thrown when the specified input is not valid. + */ + public GetNewAddressResponse getNextAvailableAddress(String seed, int security, boolean checksum) throws ArgumentException { + return generateNewAddresses(seed, security, checksum, 0, 1, false); + } + + /** + * Checks all addresses until the first unspent address is found. + * @param seed Tryte-encoded seed. It should be noted that this seed is not transferred. + * @param security Security level to be used for the private key / address. Can be 1, 2 or 3. + * @param checksum Adds 9-tryte address checksum. + * @param index Key index to start search from. + * @return GetNewAddressResponse containing an array of strings with the specified number of addresses. + * @throws ArgumentException is thrown when the specified input is not valid. + */ + public GetNewAddressResponse getNextAvailableAddress(String seed, int security, boolean checksum, int index) throws ArgumentException { + return generateNewAddresses(seed, security, checksum, index, 1, false); + } + + /** + * Generates new addresses, meaning addresses which were not spend from, according to the connected node. + * Starts at index 0, untill amount of unspent addresses are found. + * @param seed Tryte-encoded seed. It should be noted that this seed is not transferred. + * @param security Security level to be used for the private key / address. Can be 1, 2 or 3. + * @param checksum Adds 9-tryte address checksum. + * @param amount Total number of addresses to generate. + * @return GetNewAddressResponse containing an array of strings with the specified number of addresses. + * @throws ArgumentException is thrown when the specified input is not valid. + */ + public GetNewAddressResponse generateNewAddresses(String seed, int security, boolean checksum, int amount) throws ArgumentException { + return generateNewAddresses(seed, security, checksum, 0, amount, false); + } + + /** + * Generates new addresses, meaning addresses which were not spend from, according to the connected node. + * Stops when amount of unspent addresses are found,starting from index + * @param seed Tryte-encoded seed. It should be noted that this seed is not transferred. + * @param security Security level to be used for the private key / address. Can be 1, 2 or 3. + * @param checksum Adds 9-tryte address checksum. + * @param index Key index to start search from. + * @param amount Total number of addresses to generate. + * @return GetNewAddressResponse containing an array of strings with the specified number of addresses. + * @throws ArgumentException is thrown when the specified input is not valid. + */ + public GetNewAddressResponse generateNewAddresses(String seed, int security, boolean checksum, int index, int amount) throws ArgumentException { + return generateNewAddresses(seed, security, checksum, 0, amount, false); + } + + /** + * Generates new addresses, meaning addresses which were not spend from, according to the connected node. + * Stops when amount of unspent addresses are found,starting from index + * @param seed Tryte-encoded seed. It should be noted that this seed is not transferred. + * @param security Security level to be used for the private key / address. Can be 1, 2 or 3. + * @param checksum Adds 9-tryte address checksum. + * @param index Key index to start search from. + * @param amount Total number of addresses to generate. + * @param addSpendAddresses If true, it returns all addresses, even those who were determined to be spent from + * @return GetNewAddressResponse containing an array of strings with the specified number of addresses. + * @throws ArgumentException is thrown when the specified input is not valid. + */ + public GetNewAddressResponse generateNewAddresses(String seed, int security, boolean checksum, int index, int amount, boolean addSpendAddresses) throws ArgumentException { + if ((!InputValidator.isValidSeed(seed))) { + throw new IllegalStateException(Constants.INVALID_SEED_INPUT_ERROR); + } + + StopWatch stopWatch = new StopWatch(); + List allAddresses = new ArrayList<>(); - // No total provided: Continue calling findTransactions to see if address was - // already created if null, return list of addresses - for (int i = index; ; i++) { + for (int i = index, numUnspentFound=0; numUnspentFound < amount; i++) { final String newAddress = IotaAPIUtils.newAddress(seed, security, i, checksum, customCurl.clone()); final FindTransactionResponse response = findTransactionsByAddresses(newAddress); - allAddresses.add(newAddress); + if (response.getHashes().length == 0) { - break; + //Unspent address + allAddresses.add(newAddress); + numUnspentFound++; + } else if (addSpendAddresses) { + //Spend address, were interested anyways + allAddresses.add(newAddress); } } + + return GetNewAddressResponse.create(allAddresses, stopWatch.getElapsedTimeMili()); + } + + /** + * Generates amount of addresses, starting from index + * This does not mean that these addresses are safe to use (unspent) + * @param seed Tryte-encoded seed. It should be noted that this seed is not transferred. + * @param security Security level to be used for the private key / address. Can be 1, 2 or 3. + * @param checksum Adds 9-tryte address checksum. + * @param index Key index to start search from. The generation of the address is not deterministic. + * @param amount Total number of addresses to generate. + * @return GetNewAddressResponse containing an array of strings with the specified number of addresses. + * @throws ArgumentException is thrown when the specified input is not valid. + */ + public GetNewAddressResponse getAddressesUnchecked(String seed, int security, boolean checksum, int index, int amount) throws ArgumentException { + if ((!InputValidator.isValidSeed(seed))) { + throw new IllegalStateException(Constants.INVALID_SEED_INPUT_ERROR); + } + + StopWatch stopWatch = new StopWatch(); - // If !returnAll return only the last address that was generated - if (!returnAll) { - - //allAddresses = allAddresses.subList(allAddresses.size() - 2, allAddresses.size() - 1); - allAddresses = allAddresses.subList(allAddresses.size() - 1, allAddresses.size()); + List allAddresses = new ArrayList<>(); + for (int i = index; i < index + amount; i++) { + allAddresses.add(IotaAPIUtils.newAddress(seed, security, i, checksum, customCurl.clone())); } return GetNewAddressResponse.create(allAddresses, stopWatch.getElapsedTimeMili()); } @@ -105,11 +200,11 @@ public GetTransferResponse getTransfers(String seed, int security, Integer start // validate seed if ((!InputValidator.isValidSeed(seed))) { - throw new IllegalStateException(INVALID_SEED_INPUT_ERROR); + throw new IllegalStateException(Constants.INVALID_SEED_INPUT_ERROR); } if (start > end || end > (start + 500)) { - throw new ArgumentException(INVALID_INPUT_ERROR); + throw new ArgumentException(Constants.INVALID_INPUT_ERROR); } StopWatch stopWatch = new StopWatch(); @@ -167,7 +262,7 @@ public Bundle[] bundlesFromAddresses(String[] addresses, final Boolean inclusion if (tailTxArray.length != 0 && inclusionStates) { gisr = getLatestInclusion(tailTxArray); if (gisr == null || gisr.getStates() == null || gisr.getStates().length == 0) { - throw new IllegalStateException(GET_INCLUSION_STATE_RESPONSE_ERROR); + throw new IllegalStateException(Constants.GET_INCLUSION_STATE_RESPONSE_ERROR); } } final GetInclusionStateResponse finalInclusionStates = gisr; @@ -192,7 +287,7 @@ public void perform(String param) { } // If error returned from getBundle, simply ignore it because the bundle was most likely incorrect } catch (ArgumentException e) { - log.warn(GET_BUNDLE_RESPONSE_ERROR); + log.warn(Constants.GET_BUNDLE_RESPONSE_ERROR); } } }); @@ -206,24 +301,24 @@ public void perform(String param) { } /** - * Wrapper function that broadcasts and stores the specified trytes. + * Wrapper function that stores and broadcasts the specified trytes. * * @param trytes The trytes. - * @return A StoreTransactionsResponse. + * @return A BroadcastTransactionsResponse. * @throws ArgumentException is thrown when the specified input is not valid. */ - public StoreTransactionsResponse broadcastAndStore(final String... trytes) throws ArgumentException { + public BroadcastTransactionsResponse storeAndBroadcast(final String... trytes) throws ArgumentException { if (!InputValidator.isArrayOfAttachedTrytes(trytes)) { - throw new ArgumentException(INVALID_TRYTES_INPUT_ERROR); + throw new ArgumentException(Constants.INVALID_TRYTES_INPUT_ERROR); } try { - broadcastTransactions(trytes); + storeTransactions(trytes); } catch (Exception e) { throw new ArgumentException(e.toString()); } - return storeTransactions(trytes); + return broadcastTransactions(trytes); } /** @@ -243,7 +338,7 @@ public List sendTrytes(final String[] trytes, final int depth, fina final GetAttachToTangleResponse res = attachToTangle(txs.getTrunkTransaction(), txs.getBranchTransaction(), minWeightMagnitude, trytes); try { - broadcastAndStore(res.getTrytes()); + storeAndBroadcast(res.getTrytes()); } catch (ArgumentException e) { return new ArrayList<>(); } @@ -266,7 +361,7 @@ public List sendTrytes(final String[] trytes, final int depth, fina public List findTransactionsObjectsByHashes(String[] hashes) throws ArgumentException { if (!InputValidator.isArrayOfHashes(hashes)) { - throw new IllegalStateException(INVALID_HASHES_INPUT_ERROR); + throw new IllegalStateException(Constants.INVALID_HASHES_INPUT_ERROR); } final GetTrytesResponse trytesResponse = getTrytes(hashes); @@ -365,16 +460,16 @@ public List prepareTransfers(String seed, int security, final List prepareTransfers(String seed, int security, final List totalBalance) { - throw new IllegalStateException(NOT_ENOUGH_BALANCE_ERROR); + throw new IllegalStateException(Constants.NOT_ENOUGH_BALANCE_ERROR); } return addRemainder(seed, security, confirmedInputs, bundle, tag, totalValue, remainder, signatureFragments); @@ -541,17 +636,17 @@ public GetBalancesAndFormatResponse getInputs(String seed, int security, int sta // validate the seed if ((!InputValidator.isValidSeed(seed))) { - throw new IllegalStateException(INVALID_SEED_INPUT_ERROR); + throw new IllegalStateException(Constants.INVALID_SEED_INPUT_ERROR); } - if (security < 1) { - throw new ArgumentException(INVALID_SECURITY_LEVEL_INPUT_ERROR); + if (!InputValidator.isValidSecurityLevel(security)) { + throw new ArgumentException(Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR); } // 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_INPUT_ERROR); + if ((start > end && end > 0) || end > (start + 500)) { + throw new IllegalStateException(Constants.INVALID_INPUT_ERROR); } StopWatch stopWatch = new StopWatch(); @@ -597,8 +692,8 @@ public GetBalancesAndFormatResponse getInputs(String seed, int security, int sta **/ public GetBalancesAndFormatResponse getBalanceAndFormat(final List addresses, final List tips, long threshold, int start, StopWatch stopWatch, int security) throws ArgumentException, IllegalStateException { - if (security < 1) { - throw new ArgumentException(INVALID_SECURITY_LEVEL_INPUT_ERROR); + if (!InputValidator.isValidSecurityLevel(security)) { + throw new ArgumentException(Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR); } GetBalancesResponse getBalancesResponse = getBalances(100, addresses, tips); @@ -633,7 +728,7 @@ public GetBalancesAndFormatResponse getBalanceAndFormat(final List addre if (thresholdReached) { return GetBalancesAndFormatResponse.create(inputs, totalBalance, stopWatch.getElapsedTimeMili()); } - throw new IllegalStateException(NOT_ENOUGH_BALANCE_ERROR); + throw new IllegalStateException(Constants.NOT_ENOUGH_BALANCE_ERROR); } /** @@ -647,84 +742,20 @@ public GetBalancesAndFormatResponse getBalanceAndFormat(final List addre public GetBundleResponse getBundle(String transaction) throws ArgumentException { if (!InputValidator.isHash(transaction)) { - throw new ArgumentException(INVALID_HASHES_INPUT_ERROR); - } - - Bundle bundle = traverseBundle(transaction, null, new Bundle()); - if (bundle == null) { - throw new ArgumentException(INVALID_BUNDLE_ERROR); + throw new ArgumentException(Constants.INVALID_HASHES_INPUT_ERROR); } - + StopWatch stopWatch = new StopWatch(); - - long totalSum = 0; - String bundleHash = bundle.getTransactions().get(0).getBundle(); - ICurl curl = SpongeFactory.create(SpongeFactory.Mode.KERL); - curl.reset(); - - List signaturesToValidate = new ArrayList<>(); - - for (int i = 0; i < bundle.getTransactions().size(); i++) { - Transaction trx = bundle.getTransactions().get(i); - Long bundleValue = trx.getValue(); - totalSum += bundleValue; - - if (i != bundle.getTransactions().get(i).getCurrentIndex()) { - throw new ArgumentException(INVALID_BUNDLE_ERROR); - } - - String trxTrytes = trx.toTrytes().substring(2187, 2187 + 162); - // Absorb bundle hash + value + timestamp + lastIndex + currentIndex trytes. - curl.absorb(Converter.trits(trxTrytes)); - // Check if input transaction - if (bundleValue < 0) { - String address = trx.getAddress(); - Signature sig = new Signature(); - sig.setAddress(address); - sig.getSignatureFragments().add(trx.getSignatureFragments()); - - // Find the subsequent txs with the remaining signature fragment - for (int y = i + 1; y < bundle.getTransactions().size(); y++) { - Transaction newBundleTx = bundle.getTransactions().get(y); - - // Check if new tx is part of the signature fragment - if (newBundleTx.getAddress().equals(address) && newBundleTx.getValue() == 0) { - if (sig.getSignatureFragments().indexOf(newBundleTx.getSignatureFragments()) == -1) - sig.getSignatureFragments().add(newBundleTx.getSignatureFragments()); - } - } - signaturesToValidate.add(sig); - } + Bundle bundle = traverseBundle(transaction, null, new Bundle()); + if (bundle == null) { + throw new ArgumentException(Constants.INVALID_BUNDLE_ERROR); } - // Check for total sum, if not equal 0 return error - if (totalSum != 0) - throw new ArgumentException(INVALID_BUNDLE_SUM_ERROR); - int[] bundleFromTrxs = new int[243]; - curl.squeeze(bundleFromTrxs); - String bundleFromTxString = Converter.trytes(bundleFromTrxs); - - // Check if bundle hash is the same as returned by tx object - if (!bundleFromTxString.equals(bundleHash)) { - throw new ArgumentException(INVALID_BUNDLE_HASH_ERROR); - } + if (!BundleValidator.isBundle(bundle)){ + throw new ArgumentException(Constants.INVALID_BUNDLE_ERROR); + } - // Last tx in the bundle should have currentIndex === lastIndex - bundle.setLength(bundle.getTransactions().size()); - if (!(bundle.getTransactions().get(bundle.getLength() - 1).getCurrentIndex() == (bundle.getTransactions().get(bundle.getLength() - 1).getLastIndex()))) - throw new ArgumentException(INVALID_BUNDLE_ERROR); - - // Validate the signatures - for (Signature aSignaturesToValidate : signaturesToValidate) { - String[] signatureFragments = aSignaturesToValidate.getSignatureFragments().toArray(new String[aSignaturesToValidate.getSignatureFragments().size()]); - String address = aSignaturesToValidate.getAddress(); - boolean isValidSignature = new Signing(customCurl.clone()).validateSignatures(address, signatureFragments, bundleHash); - - if (!isValidSignature) - throw new ArgumentException(INVALID_SIGNATURES_ERROR); - } - return GetBundleResponse.create(bundle.getTransactions(), stopWatch.getElapsedTimeMili()); } @@ -744,9 +775,12 @@ public GetBundleResponse getBundle(String transaction) throws ArgumentException * @throws ArgumentException is thrown when the specified input is not valid. */ public GetAccountDataResponse getAccountData(String seed, int security, int index, boolean checksum, int total, boolean returnAll, int start, int end, boolean inclusionStates, long threshold) throws ArgumentException { - + if (!InputValidator.isValidSecurityLevel(security)) { + throw new ArgumentException(Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR); + } + if (start > end || end > (start + 1000)) { - throw new ArgumentException(INVALID_INPUT_ERROR); + throw new ArgumentException(Constants.INVALID_INPUT_ERROR); } StopWatch stopWatch = new StopWatch(); @@ -813,7 +847,7 @@ public Boolean checkWereAddressSpentFrom(String address) throws ArgumentExceptio public ReplayBundleResponse replayBundle(String tailTransactionHash, int depth, int minWeightMagnitude, String reference) throws ArgumentException { if (!InputValidator.isHash(tailTransactionHash)) { - throw new ArgumentException(INVALID_TAIL_HASH_INPUT_ERROR); + throw new ArgumentException(Constants.INVALID_TAIL_HASH_INPUT_ERROR); } StopWatch stopWatch = new StopWatch(); @@ -904,8 +938,8 @@ public SendTransferResponse sendTransfer(String seed, int security, int depth, i * transaction hash is not a tail, we return an error. * * @param trunkTx Hash of a trunk or a tail transaction of a bundle. - * @param bundleHash The bundle hashes. - * @param bundle List of bundles to be populated. + * @param bundleHash The bundle hash. + * @param bundle bundle to be populated. * @return Transaction objects. * @throws ArgumentException is thrown when an invalid input is provided. */ @@ -915,16 +949,16 @@ public Bundle traverseBundle(String trunkTx, String bundleHash, Bundle bundle) t if (gtr != null) { if (gtr.getTrytes().length == 0) { - throw new ArgumentException(INVALID_BUNDLE_ERROR); + throw new ArgumentException(Constants.INVALID_BUNDLE_ERROR); } Transaction trx = new Transaction(gtr.getTrytes()[0], customCurl.clone()); if (trx.getBundle() == null) { - throw new ArgumentException(INVALID_TRYTES_INPUT_ERROR); + throw new ArgumentException(Constants.INVALID_TRYTES_INPUT_ERROR); } // If first transaction to search is not a tail, return error if (bundleHash == null && trx.getCurrentIndex() != 0) { - throw new ArgumentException(INVALID_TAIL_HASH_INPUT_ERROR); + throw new ArgumentException(Constants.INVALID_TAIL_HASH_INPUT_ERROR); } // If no bundle hash, define it if (bundleHash == null) { @@ -932,6 +966,7 @@ public Bundle traverseBundle(String trunkTx, String bundleHash, Bundle bundle) t } // If different bundle hash, return with bundle if (!bundleHash.equals(trx.getBundle())) { + bundle.setLength(bundle.getTransactions().size()); return bundle; } // If only one bundle element, return @@ -946,7 +981,7 @@ public Bundle traverseBundle(String trunkTx, String bundleHash, Bundle bundle) t // Continue traversing with new trunkTx return traverseBundle(trunkTx, bundleHash, bundle); } else { - throw new ArgumentException(GET_TRYTES_RESPONSE_ERROR); + throw new ArgumentException(Constants.GET_TRYTES_RESPONSE_ERROR); } } @@ -1020,19 +1055,22 @@ public List initiateTransfer(int securitySum, String inputAddress, public List initiateTransfer(int securitySum, String inputAddress, String remainderAddress, List transfers, List tips, boolean testMode) throws ArgumentException { - + if (securitySum < Constants.MIN_SECURITY_LEVEL) { + throw new ArgumentException(Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR); + } + // validate input address if (!InputValidator.isAddress(inputAddress)) - throw new ArgumentException(INVALID_ADDRESSES_INPUT_ERROR); + throw new ArgumentException(Constants.INVALID_ADDRESSES_INPUT_ERROR); // validate remainder address if (remainderAddress != null && !InputValidator.isAddress(remainderAddress)) { - throw new ArgumentException(INVALID_ADDRESSES_INPUT_ERROR); + throw new ArgumentException(Constants.INVALID_ADDRESSES_INPUT_ERROR); } // Input validation of transfers object if (!InputValidator.isTransfersCollectionValid(transfers)) { - throw new ArgumentException(INVALID_TRANSFERS_INPUT_ERROR); + throw new ArgumentException(Constants.INVALID_TRANSFERS_INPUT_ERROR); } // Create a new bundle @@ -1145,7 +1183,7 @@ public List initiateTransfer(int securitySum, String inputAddress, } // Return not enough balance error if (totalValue > totalBalance) { - throw new IllegalStateException(NOT_ENOUGH_BALANCE_ERROR); + throw new IllegalStateException(Constants.NOT_ENOUGH_BALANCE_ERROR); } // If there is a remainder value @@ -1156,7 +1194,7 @@ public List initiateTransfer(int securitySum, String inputAddress, // Remainder bundle entry if necessary if (remainderAddress == null) { - throw new IllegalStateException(NO_REMAINDER_ADDRESS_ERROR); + throw new IllegalStateException(Constants.NO_REMAINDER_ADDRESS_ERROR); } bundle.addEntry(1, remainderAddress, remainder, tag, timestamp); @@ -1167,7 +1205,7 @@ public List initiateTransfer(int securitySum, String inputAddress, return bundle.getTransactions(); } else { - throw new RuntimeException(INVALID_VALUE_TRANSFER_ERROR); + throw new RuntimeException(Constants.INVALID_VALUE_TRANSFER_ERROR); } } @@ -1282,10 +1320,47 @@ public List addRemainder(final String seed, totalTransferValue -= thisBalance; } } - throw new IllegalStateException(NOT_ENOUGH_BALANCE_ERROR); + throw new IllegalStateException(Constants.NOT_ENOUGH_BALANCE_ERROR); } - + /** + * Checks if a transaction hash is promotable + * @param tail the transaction we want to promote + * @return true if it is, otherwise false + * @throws ArgumentException when we can't get the consistency of this transaction + */ + public boolean isPromotable(Transaction tail) throws ArgumentException { + long lowerBound = tail.getAttachmentTimestamp(); + CheckConsistencyResponse consistencyResponse = checkConsistency(tail.getHash()); + + return consistencyResponse.getState() && isAboveMaxDepth(lowerBound); + } + + /** + * Checks if a transaction hash is promotable + * @param tail the transaction hash we want to check + * @return true if it is, otherwise false + * @throws ArgumentException when we can't get the consistency of this transaction + * or when the transaction is not found + */ + public boolean isPromotable(String tail) throws ArgumentException { + GetTrytesResponse transaction = getTrytes(tail); + if (0 == transaction.getTrytes().length) { + throw new ArgumentException(Constants.TRANSACTION_NOT_FOUND); + } + + return isPromotable(new Transaction(transaction.getTrytes()[0])); + } + + private boolean isAboveMaxDepth (long attachmentTimestamp) { + // Check against future timestamps + return attachmentTimestamp < System.currentTimeMillis() && + // Check if transaction wasn't issued before last 6 milestones + // Milestones are being issued every ~2mins + System.currentTimeMillis() - attachmentTimestamp < 11 * 60 * 1000; + } + + /** * Attempts to promote a transaction using a provided bundle and, if successful, returns the promoting Transactions. * @@ -1322,7 +1397,7 @@ public List promoteTransaction(final String tail, final int depth, bundle.getTransactions().stream().map(tx -> tx.toTrytes()).toArray(String[]::new)); try { - broadcastAndStore(res.getTrytes()); + storeAndBroadcast(res.getTrytes()); } catch (ArgumentException e) { return Collections.emptyList(); } diff --git a/jota/src/main/java/jota/dto/response/GetNewAddressResponse.java b/jota/src/main/java/jota/dto/response/GetNewAddressResponse.java index d9fc5adc..e2ce8d79 100644 --- a/jota/src/main/java/jota/dto/response/GetNewAddressResponse.java +++ b/jota/src/main/java/jota/dto/response/GetNewAddressResponse.java @@ -27,4 +27,12 @@ public static GetNewAddressResponse create(List addresses, long duration public List getAddresses() { return addresses; } + + /** + * Gets the first address, for quick access + * @return The address + */ + public String first() { + return addresses.get(0); + } } diff --git a/jota/src/main/java/jota/dto/response/GetNodeInfoResponse.java b/jota/src/main/java/jota/dto/response/GetNodeInfoResponse.java index 132a4559..e23a1298 100644 --- a/jota/src/main/java/jota/dto/response/GetNodeInfoResponse.java +++ b/jota/src/main/java/jota/dto/response/GetNodeInfoResponse.java @@ -21,6 +21,8 @@ public class GetNodeInfoResponse extends AbstractResponse { private long time; private int tips; private int transactionsToRequest; + + private String[] features; /** * The name of the IOTA software the node currently running (IRI stands for Initial Reference Implementation). @@ -167,4 +169,12 @@ public int getTips() { public int getTransactionsToRequest() { return transactionsToRequest; } + + /** + * + * @return + */ + public String[] getFeatures() { + return features; + } } \ No newline at end of file diff --git a/jota/src/main/java/jota/error/BaseException.java b/jota/src/main/java/jota/error/BaseException.java index b1f91cc0..6a4ce901 100644 --- a/jota/src/main/java/jota/error/BaseException.java +++ b/jota/src/main/java/jota/error/BaseException.java @@ -4,9 +4,6 @@ import java.util.Arrays; import java.util.Collection; -/** - * @author Adrian - */ public class BaseException extends Exception { /** diff --git a/jota/src/main/java/jota/model/Bundle.java b/jota/src/main/java/jota/model/Bundle.java index 3ed38406..a5bde369 100644 --- a/jota/src/main/java/jota/model/Bundle.java +++ b/jota/src/main/java/jota/model/Bundle.java @@ -3,6 +3,8 @@ import jota.pow.ICurl; import jota.pow.SpongeFactory; import jota.utils.Converter; +import jota.utils.Signing; + import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; @@ -12,7 +14,6 @@ /** * This class represents a Bundle, a set of transactions. * - * @author pinpong **/ public class Bundle implements Comparable { @@ -25,7 +26,7 @@ public class Bundle implements Comparable { * Initializes a new instance of the Bundle class without transactions. */ public Bundle() { - this(new ArrayList(), 0); + this(new ArrayList<>(), 0); } /** @@ -181,41 +182,7 @@ public void addTrytes(List signatureFragments) { * @return normalizedBundle A normalized bundle hash. */ public int[] normalizedBundle(String bundleHash) { - int[] normalizedBundle = new int[81]; - - 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.tritsString("" + 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; + return new Signing().normalizedBundle(bundleHash); } diff --git a/jota/src/main/java/jota/model/Input.java b/jota/src/main/java/jota/model/Input.java index 0e33c38c..285e3f19 100644 --- a/jota/src/main/java/jota/model/Input.java +++ b/jota/src/main/java/jota/model/Input.java @@ -6,7 +6,6 @@ /** * This class represents an Input. * - * @author Adrian **/ public class Input { diff --git a/jota/src/main/java/jota/model/Inputs.java b/jota/src/main/java/jota/model/Inputs.java index 8d142e10..11a05525 100644 --- a/jota/src/main/java/jota/model/Inputs.java +++ b/jota/src/main/java/jota/model/Inputs.java @@ -47,7 +47,7 @@ public List getInputsList() { * @param inputsList The input list. */ public void setInputsList(List inputsList) { - inputsList = inputsList; + this.inputsList = inputsList; } /** diff --git a/jota/src/main/java/jota/model/Neighbor.java b/jota/src/main/java/jota/model/Neighbor.java index 48daa618..2bbe89eb 100644 --- a/jota/src/main/java/jota/model/Neighbor.java +++ b/jota/src/main/java/jota/model/Neighbor.java @@ -3,7 +3,6 @@ /** * This class represents an Neighbor. * - * @author pinpong **/ public class Neighbor { diff --git a/jota/src/main/java/jota/model/Signature.java b/jota/src/main/java/jota/model/Signature.java index 02f9b014..7e985a8d 100644 --- a/jota/src/main/java/jota/model/Signature.java +++ b/jota/src/main/java/jota/model/Signature.java @@ -6,7 +6,6 @@ /** * This class represents an Signature. * - * @author Adrian **/ public class Signature { diff --git a/jota/src/main/java/jota/model/Transaction.java b/jota/src/main/java/jota/model/Transaction.java index c9782dfe..b74933a9 100644 --- a/jota/src/main/java/jota/model/Transaction.java +++ b/jota/src/main/java/jota/model/Transaction.java @@ -2,7 +2,10 @@ import jota.pow.ICurl; import jota.pow.SpongeFactory; +import jota.utils.Constants; import jota.utils.Converter; +import jota.utils.InputValidator; + import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -14,7 +17,6 @@ /** * This class represents an iota transaction. * - * @author pinpong */ public class Transaction { @@ -39,6 +41,28 @@ public class Transaction { private String tag; private long attachmentTimestampLowerBound; private long attachmentTimestampUpperBound; + + /** + * Converts an array of transaction trytes into an array of transaction objects. + * @param trytes the array of transactions trytes + * @return the transaction objects + */ + public static Transaction[] asTransactionObjects(String... trytes) { + Transaction[] transactions = new Transaction[trytes.length]; + for (int i = 0; i < trytes.length; i++) { + transactions[i] = asTransactionObject(trytes[i]); + } + return transactions; + } + + /** + * Converts transaction trytes into a transaction object. + * @param trytes the transaction trytes + * @return the transaction object + */ + public static Transaction asTransactionObject(String trytes) { + return new Transaction(trytes); + } /** * Initializes a new instance of the Signature class. @@ -73,15 +97,7 @@ public Transaction(String address, long value, String tag, long timestamp) { this.obsoleteTag = tag; this.timestamp = timestamp; } - - public long getAttachmentTimestampLowerBound() { - return attachmentTimestampLowerBound; - } - - public void setAttachmentTimestampLowerBound(long attachmentTimestampLowerBound) { - this.attachmentTimestampLowerBound = attachmentTimestampLowerBound; - } - + /** * Initializes a new instance of the Signature class. */ @@ -110,6 +126,14 @@ public Transaction(String trytes, ICurl customCurl) { transactionObject(trytes); this.customCurl = customCurl; } + + public long getAttachmentTimestampLowerBound() { + return attachmentTimestampLowerBound; + } + + public void setAttachmentTimestampLowerBound(long attachmentTimestampLowerBound) { + this.attachmentTimestampLowerBound = attachmentTimestampLowerBound; + } public long getAttachmentTimestampUpperBound() { return attachmentTimestampUpperBound; @@ -452,15 +476,13 @@ public void transactionObject(final String trytes) { } // 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; - } + if (!InputValidator.isNinesTrytes(trytes.substring(2279, 2295), 16)) { + log.warn("Trytes {} does not seem a valid tryte", trytes); + return; } int[] transactionTrits = Converter.trits(trytes); - int[] hash = new int[243]; + int[] hash = new int[Constants.HASH_LENGTH_TRITS]; ICurl curl = SpongeFactory.create(SpongeFactory.Mode.CURLP81); // generate the correct transaction hash @@ -469,20 +491,28 @@ public void transactionObject(final String trytes) { curl.squeeze(hash, 0, hash.length); this.setHash(Converter.trytes(hash)); - this.setSignatureFragments(trytes.substring(0, 2187)); - this.setAddress(trytes.substring(2187, 2268)); + this.setSignatureFragments(trytes.substring(0, Constants.MESSAGE_LENGTH)); + this.setAddress(trytes.substring(Constants.MESSAGE_LENGTH, Constants.MESSAGE_LENGTH + Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM)); this.setValue(Converter.longValue(Arrays.copyOfRange(transactionTrits, 6804, 6837))); - this.setObsoleteTag(trytes.substring(2295, 2322)); + this.setObsoleteTag(trytes.substring(2295, 2295 + Constants.TAG_LENGTH)); this.setTimestamp(Converter.longValue(Arrays.copyOfRange(transactionTrits, 6966, 6993))); this.setCurrentIndex(Converter.longValue(Arrays.copyOfRange(transactionTrits, 6993, 7020))); this.setLastIndex(Converter.longValue(Arrays.copyOfRange(transactionTrits, 7020, 7047))); - this.setBundle(trytes.substring(2349, 2430)); - this.setTrunkTransaction(trytes.substring(2430, 2511)); - this.setBranchTransaction(trytes.substring(2511, 2592)); - this.setTag(trytes.substring(2592, 2619)); + this.setBundle(trytes.substring(2349, 2349 + Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM)); + this.setTrunkTransaction(trytes.substring(2430, 2430 + Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM)); + this.setBranchTransaction(trytes.substring(2511, 2511 + Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM)); + this.setTag(trytes.substring(2592, 2592 + Constants.TAG_LENGTH)); this.setAttachmentTimestamp(Converter.longValue(Arrays.copyOfRange(transactionTrits, 7857, 7884))); this.setAttachmentTimestampLowerBound(Converter.longValue(Arrays.copyOfRange(transactionTrits, 7884, 7911))); this.setAttachmentTimestampUpperBound(Converter.longValue(Arrays.copyOfRange(transactionTrits, 7911, 7938))); this.setNonce(trytes.substring(2646, 2673)); } + + /** + * Checks if the current index is 0 + * @return if this is a tail transaction + */ + public boolean isTailTransaction() { + return getCurrentIndex() == 0; + } } diff --git a/jota/src/main/java/jota/model/Transfer.java b/jota/src/main/java/jota/model/Transfer.java index 0b33b1bb..ffffd9b0 100644 --- a/jota/src/main/java/jota/model/Transfer.java +++ b/jota/src/main/java/jota/model/Transfer.java @@ -5,7 +5,6 @@ /** * This class represents a Transfer. * - * @author pinpong */ public class Transfer { diff --git a/jota/src/main/java/cfb/pearldiver/PearlDiver.java b/jota/src/main/java/jota/pow/pearldiver/PearlDiver.java similarity index 98% rename from jota/src/main/java/cfb/pearldiver/PearlDiver.java rename to jota/src/main/java/jota/pow/pearldiver/PearlDiver.java index 5ec91f66..42358fbc 100644 --- a/jota/src/main/java/cfb/pearldiver/PearlDiver.java +++ b/jota/src/main/java/jota/pow/pearldiver/PearlDiver.java @@ -1,9 +1,9 @@ -package cfb.pearldiver; +package jota.pow.pearldiver; -import static cfb.pearldiver.PearlDiver.State.CANCELLED; -import static cfb.pearldiver.PearlDiver.State.COMPLETED; -import static cfb.pearldiver.PearlDiver.State.RUNNING; import static jota.pow.JCurl.NUMBER_OF_ROUNDSP81; +import static jota.pow.pearldiver.PearlDiver.State.CANCELLED; +import static jota.pow.pearldiver.PearlDiver.State.COMPLETED; +import static jota.pow.pearldiver.PearlDiver.State.RUNNING; /** diff --git a/jota/src/main/java/cfb/pearldiver/PearlDiverLocalPoW.java b/jota/src/main/java/jota/pow/pearldiver/PearlDiverLocalPoW.java similarity index 95% rename from jota/src/main/java/cfb/pearldiver/PearlDiverLocalPoW.java rename to jota/src/main/java/jota/pow/pearldiver/PearlDiverLocalPoW.java index a470be68..7d2d8112 100644 --- a/jota/src/main/java/cfb/pearldiver/PearlDiverLocalPoW.java +++ b/jota/src/main/java/jota/pow/pearldiver/PearlDiverLocalPoW.java @@ -1,4 +1,4 @@ -package cfb.pearldiver; +package jota.pow.pearldiver; import jota.IotaLocalPoW; import jota.utils.Converter; diff --git a/jota/src/main/java/jota/utils/BundleValidator.java b/jota/src/main/java/jota/utils/BundleValidator.java new file mode 100644 index 00000000..2eb2f04f --- /dev/null +++ b/jota/src/main/java/jota/utils/BundleValidator.java @@ -0,0 +1,145 @@ +package jota.utils; + +import static jota.utils.Constants.INVALID_BUNDLE_ERROR; +import static jota.utils.Constants.INVALID_BUNDLE_HASH_ERROR; +import static jota.utils.Constants.INVALID_BUNDLE_SUM_ERROR; +import static jota.utils.Constants.INVALID_SIGNATURES_ERROR; + +import static jota.pow.JCurl.HASH_LENGTH; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import jota.error.ArgumentException; +import jota.model.Bundle; +import jota.model.Transaction; +import jota.pow.ICurl; +import jota.pow.SpongeFactory; + +public class BundleValidator { + + /** + * Validates all signatures of a bundle + * @param bundle the bundle + * @param bundleHash + * @param customCurl + * @return true if all signatures are valid. Otherwise false + */ + public static boolean validateSignatures(Bundle bundle, ICurl customCurl) { + for (int i = 0; i < bundle.getLength(); i++) { + Transaction tx = bundle.getTransactions().get(i); + + // check whether input transaction + if (tx.getValue() >= 0) { + continue; + } + + List fragments = new ArrayList<>(); + fragments.add(tx.getSignatureFragments()); + + // find the subsequent txs containing the remaining signature + // message fragments for this input transaction + for (int j = i; j < bundle.getLength()-1; j++ ){ + Transaction otherTx = bundle.getTransactions().get(j+1); + if (otherTx.getValue() != 0 || !otherTx.getAddress().equals(tx.getAddress())) { + continue; + } + + fragments.add(otherTx.getSignatureFragments()); + } + + boolean valid = new Signing(customCurl).validateSignatures(tx.getAddress(), fragments.toArray(new String[fragments.size()]), tx.getBundle()); + if (!valid) { + return false; + } + + } + return true; + } + + /** + * Checks if a bundle is syntactically valid. + * Validates signatures and overall structure + * @param bundle the bundle to verify + * @return true if the bundle is valid. + * @throws ArgumentException if there is an error with the bundle + */ + public static boolean isBundle(Bundle bundle) throws ArgumentException { + return isBundle(bundle, null); + } + + /** + * Checks if a bundle is syntactically valid. + * Validates signatures and overall structure + * @param bundle the bundle to verify + * @param customCurlMode + * @return true if the bundle is valid. + * @throws ArgumentException if there is an error with the bundle + */ + public static boolean isBundle(Bundle bundle, Optional customCurlMode) throws ArgumentException { + ICurl curl = null; + if (null == customCurlMode || !customCurlMode.isPresent()) { + curl = SpongeFactory.create(SpongeFactory.Mode.KERL); + } else { + curl = SpongeFactory.create(customCurlMode.get()); + } + + int totalSum = 0; + int lastIndex = bundle.getLength() - 1; + + for (int i = 0; i < bundle.getLength(); i++) { + Transaction tx = bundle.getTransactions().get(i); + totalSum += tx.getValue(); + + if (tx.getCurrentIndex() != i ){ + throw new ArgumentException(INVALID_BUNDLE_ERROR); + } + if (tx.getLastIndex() != lastIndex ){ + throw new ArgumentException(INVALID_BUNDLE_ERROR); + } + + int[] txTrits = Converter.trits(tx.toTrytes().substring(2187, 2187 + 162)); + curl.absorb(txTrits); + + // continue if output or signature tx + if (tx.getValue() >= 0 ){ + continue; + } + + // here we have an input transaction (negative value) + List fragments = new ArrayList<>(); + fragments.add(tx.getSignatureFragments()); + + // find the subsequent txs containing the remaining signature + // message fragments for this input transaction + for (int j = i; j < bundle.getLength()-1; j++) { + Transaction tx2 = bundle.getTransactions().get(j+1); + + // check if the tx is part of the input transaction + if (tx.getAddress().equals(tx2.getAddress()) && tx2.getValue() == 0) { + // append the signature message fragment + fragments.add(tx2.getSignatureFragments()); + } + } + boolean valid = new Signing(curl.clone()).validateSignatures(tx.getAddress(), fragments.toArray(new String[fragments.size()]), tx.getBundle()); + if (!valid) { + throw new ArgumentException(INVALID_SIGNATURES_ERROR); + } + } + + // sum of all transaction must be 0 + if (totalSum != 0) { + throw new ArgumentException(INVALID_BUNDLE_SUM_ERROR); + } + + int[] bundleHashTrits = new int[HASH_LENGTH]; + curl.squeeze(bundleHashTrits, 0, HASH_LENGTH); + String bundleHash = Converter.trytes(bundleHashTrits); + + if (!bundleHash.equals(bundle.getTransactions().get(0).getBundle())) { + throw new ArgumentException(INVALID_BUNDLE_HASH_ERROR); + } + return true; + } +} diff --git a/jota/src/main/java/jota/utils/Constants.java b/jota/src/main/java/jota/utils/Constants.java index 3b40a237..f184e64f 100644 --- a/jota/src/main/java/jota/utils/Constants.java +++ b/jota/src/main/java/jota/utils/Constants.java @@ -3,9 +3,13 @@ /** * This class defines the global constants. * - * @author pinpong */ public class Constants { + + /** + * + */ + public final static int KEY_LENGTH = 6561; /** * This String contains all possible characters of the tryte alphabet @@ -16,43 +20,67 @@ public class Constants { * The maximum seed length */ public static final int SEED_LENGTH_MAX = 81; + + /** + * The length of a hash in trits + */ + public static int HASH_LENGTH_TRITS = 243; /** - * The length of an address without checksum + * The length of an address without checksum in trytes */ public static int ADDRESS_LENGTH_WITHOUT_CHECKSUM = 81; /** - * The length of an address with checksum + * The length of an address with checksum in trytes */ public static int ADDRESS_LENGTH_WITH_CHECKSUM = 90; /** - * The length of an message + * The length of a message */ public static int MESSAGE_LENGTH = 2187; + + /** + * Size of a full transaction, whether it has been attached or not. + */ + public static int TRANSACTION_SIZE = 2673; /** * The length of an tag */ public static int TAG_LENGTH = 27; + + /** + * Maximum security level of an address + */ + public static int MIN_SECURITY_LEVEL = 1; + + /** + * Minimum security level of an address + */ + public static int MAX_SECURITY_LEVEL = 3; public static final String INVALID_TRYTES_INPUT_ERROR = "Invalid trytes provided."; public static final String INVALID_HASHES_INPUT_ERROR = "Invalid hashes provided."; public static final String INVALID_TAIL_HASH_INPUT_ERROR = "Invalid tail hash provided."; public static final String INVALID_SEED_INPUT_ERROR = "Invalid seed provided."; public static final String INVALID_SECURITY_LEVEL_INPUT_ERROR = "Invalid security level provided."; + public static final String INVALID_INDEX_INPUT_ERROR = "Invalid index provided."; public static final String INVALID_ATTACHED_TRYTES_INPUT_ERROR = "Invalid attached trytes provided."; public static final String INVALID_TRANSFERS_INPUT_ERROR = "Invalid transfers provided."; public static final String INVALID_ADDRESSES_INPUT_ERROR = "Invalid addresses provided."; public static final String INVALID_INPUT_ERROR = "Invalid input provided."; - + public static final String INVALID_TAG_ERROR = "Invalid tag provided."; + public static final String INVALID_BUNDLE_ERROR = "Invalid bundle."; public static final String INVALID_BUNDLE_SUM_ERROR = "Invalid bundle sum."; public static final String INVALID_BUNDLE_HASH_ERROR = "Invalid bundle hash."; public static final String INVALID_SIGNATURES_ERROR = "Invalid signatures."; public static final String INVALID_VALUE_TRANSFER_ERROR = "Invalid value transfer: the transfer does not require a signature."; + public static final String TRANSACTION_NOT_FOUND = "Transaction was not found on the node"; + public static final String NOT_ENOUGH_BALANCE_ERROR = "Not enough balance."; public static final String NO_REMAINDER_ADDRESS_ERROR = "No remainder address defined."; diff --git a/jota/src/main/java/jota/utils/Converter.java b/jota/src/main/java/jota/utils/Converter.java index b86fecc9..cfd87e54 100644 --- a/jota/src/main/java/jota/utils/Converter.java +++ b/jota/src/main/java/jota/utils/Converter.java @@ -197,9 +197,9 @@ public static int[] copyTrits(final String input, final int[] destination) { } /** - * Converts trites to trytes. + * Converts trits to trytes. * - * @param trits Teh trits to be converted. + * @param trits The trits to be converted. * @param offset The offset to start from. * @param size The size. * @return The trytes. @@ -248,6 +248,45 @@ public static int value(final int[] trits) { } return value; } + + /** + * Converts the specified integer to its corresponding trits value. + * + * @param value the integer we want to convert + * @return The array of trits + */ + public static int[] fromValue(int value) { + if (0 == value) { + return new int[] {0}; + } + + int[] destination = new int[ + (int) (1 + Math.floor(Math.log(2 * Math.max(1, Math.abs(value))) / Math.log(3))) + ]; + + int i = 0; + int absoluteValue = value < 0 ? -value : value; + while (absoluteValue > 0) { + int remainder = absoluteValue % RADIX; + absoluteValue = (int) Math.floor(absoluteValue / RADIX); + + if (remainder > MAX_TRIT_VALUE) { + remainder = MIN_TRIT_VALUE; + absoluteValue++; + } + + destination[i] = remainder; + i++; + } + + if (value < 0) { + for (int j = 0; j < destination.length; j++) { + destination[j] = -destination[j]; + } + } + + return destination; + } /** * Converts the specified trits to its corresponding integer value. diff --git a/jota/src/main/java/jota/utils/ExtractJson.java b/jota/src/main/java/jota/utils/ExtractJson.java new file mode 100644 index 00000000..ea1ece6e --- /dev/null +++ b/jota/src/main/java/jota/utils/ExtractJson.java @@ -0,0 +1,15 @@ +package jota.utils; + +import com.google.gson.JsonObject; + +import jota.model.Bundle; + +public class ExtractJson { + public static JsonObject fromBundle(Bundle bundle) { + JsonObject json = new JsonObject(); + + + + return json; + } +} diff --git a/jota/src/main/java/jota/utils/InputValidator.java b/jota/src/main/java/jota/utils/InputValidator.java index 9f636ad9..dac05302 100644 --- a/jota/src/main/java/jota/utils/InputValidator.java +++ b/jota/src/main/java/jota/utils/InputValidator.java @@ -1,18 +1,21 @@ package jota.utils; import jota.error.ArgumentException; +import jota.model.Input; import jota.model.Transfer; import org.apache.commons.lang3.math.NumberUtils; +import java.net.URI; +import java.net.URISyntaxException; import java.util.List; import static jota.utils.Constants.INVALID_ADDRESSES_INPUT_ERROR; +import static jota.utils.Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR; import static jota.utils.Constants.INVALID_TRANSFERS_INPUT_ERROR; /** * This class provides methods to validate the parameters of different iota API methods. * - * @author pinpong */ public class InputValidator { @@ -24,7 +27,7 @@ public class InputValidator { **/ public static boolean isAddress(String address) { return (address.length() == Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM || - address.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM) && isTrytes(address, address.length()); + address.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM) && isTrytes(address); } /** @@ -62,7 +65,7 @@ public static boolean isAddressesArrayValid(String[] addresses) throws ArgumentE * * @param address The address to validate. * @return true if the specified string is an address; otherwise, false. - * @throws ArgumentException is thrown when the specified input is not valid. + * @throws ArgumentException when the specified input is not valid. **/ public static boolean checkAddress(String address) throws ArgumentException { if (!isAddress(address)) { @@ -72,15 +75,49 @@ public static boolean checkAddress(String address) throws ArgumentException { } /** - * Determines whether the specified string contains only characters from the trytes alphabet (see ). + * Determines whether the specified string contains only characters from the trytes alphabet + * @param trytes The trytes to validate. + * @return true if the specified trytes are trytes, otherwise false. + */ + public static boolean isTrytes(String trytes) { + return isTrytesOfExactLength(trytes, 0); + } + + /** + * Determines whether the specified string contains only characters from the trytes alphabet. * * @param trytes The trytes to validate. * @param length The length. - * @return true if the specified trytes are trytes otherwise, false. + * @return true if the specified trytes are trytes and have the correct size, otherwise false. **/ - public static boolean isTrytes(final String trytes, final int length) { + public static boolean isTrytesOfExactLength(String trytes, int length) { + if (length < 0 ) return false; + return trytes.matches("^[A-Z9]{" + (length == 0 ? "0," : length) + "}$"); } + + /** + * Determines whether the specified string contains only characters from the trytes alphabet + * and has a maximum (including) of the provided length + * @param trytes The trytes to validate. + * @param maxLength The length. + * @return true if the specified trytes are trytes and have the correct size, otherwise false. + */ + public static boolean isTrytesOfMaxLength(String trytes, int maxLength) { + if (trytes.length() > maxLength) return false; + return isTrytesOfExactLength(trytes, 0); + } + + /** + * Determines whether the specified string consist only of '9'. + * + * @param trytes The trytes to validate. + * @param length The length. + * @return true if the specified string consist only of '9'; otherwise, false. + **/ + public static boolean isEmptyTrytes(final String trytes) { + return isNinesTrytes(trytes, 0); + } /** * Determines whether the specified string consist only of '9'. @@ -104,15 +141,16 @@ public static boolean isValue(final String value) { } /** - * Determines whether the specified string array contains only trytes. - * + * Deprecated due to ambigue function name, please switch to {@link #areTransactionTrytes} + * Determines whether the specified string array contains only trytes of a transaction length * @param trytes The trytes array to validate. * @return true if the specified array contains only valid trytes otherwise, false. **/ + @Deprecated public static boolean isArrayOfTrytes(String[] trytes) { for (String tryte : trytes) { // Check if correct 2673 trytes - if (!isTrytes(tryte, 2673)) { + if (!isTrytesOfExactLength(tryte, Constants.TRANSACTION_SIZE)) { return false; } } @@ -130,15 +168,8 @@ public static boolean isArrayOfHashes(String[] hashes) { 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; - } + if (!isHash(hash)) { + return false; } } return true; @@ -149,6 +180,7 @@ public static boolean isArrayOfHashes(String[] hashes) { * * @param transfers The transfers list to validate. * @return true if the specified transfers are valid; otherwise, false. + * @throws ArgumentException when the specified input is not valid. **/ public static boolean isTransfersCollectionValid(final List transfers) throws ArgumentException { @@ -183,26 +215,96 @@ public static boolean isValidTransfer(final Transfer transfer) { } // Check if message is correct trytes encoded of any length - if (transfer.getMessage() == null || !isTrytes(transfer.getMessage(), transfer.getMessage().length())) { + if (transfer.getMessage() == null || !isTrytesOfExactLength(transfer.getMessage(), transfer.getMessage().length())) { return false; } // Check if tag is correct trytes encoded and not longer than 27 trytes - if (transfer.getTag() == null || !isTrytes(transfer.getTag(), transfer.getTag().length()) || transfer.getTag().length() > Constants.TAG_LENGTH) { + return isValidTag(transfer.getTag()); + } + + /** + * Checks if the tags are valid. + * + * @param tags The tags to validate. + * @return true if the specified tags are valid; otherwise, false. + **/ + public static boolean areValidTags(String... tags) { + // Input validation of tags + if (tags == null || tags.length == 0) { return false; } + for (final String tag : tags) { + if (!isValidTag(tag)) { + return false; + } + } return true; } + + /** + * Checks if the tag is valid. + * + * @param tag The tag to validate. + * @return true if the specified tag is valid; otherwise, false. + **/ + public static boolean isValidTag(String tag) { + return tag != null && tag.length() <= Constants.TAG_LENGTH && isTrytes(tag); + } + + /** + * Checks if the inputs are valid. + * + * @param inputs The inputs to validate. + * @return true if the specified inputs are valid; otherwise, false. + **/ + public static boolean areValidInputs(Input... inputs){ + // Input validation of input objects + if (inputs == null || inputs.length == 0) { + return false; + } + for (final Input input : inputs) { + if (!isValidInput(input)) { + return false; + } + } + return true; + } + /** - * Checks if the seed is valid. If not, an exception is thrown. + * Checks if the input is valid. * - * @param seed The seed to validate. - * @return true if the specified seed is valid; otherwise, false. + * @param input The input to validate. + * @return true if the specified input is valid; otherwise, false. + **/ + public static boolean isValidInput(Input input){ + if (input == null) { + return false; + } + + if (!isAddress(input.getAddress())) { + return false; + } + + if (input.getKeyIndex() < 0) { + return false; + } + + return isValidSecurityLevel(input.getSecurity()); + } + + /** + * Checks if the seed is valid. + * + * @param seed The input to validate. + * @return true if the specified input is valid; otherwise, false. **/ public static boolean isValidSeed(String seed) { - return isTrytes(seed, seed.length()); + if (seed.length() > Constants.SEED_LENGTH_MAX) return false; + + return isTrytes(seed); } /** @@ -212,14 +314,17 @@ public static boolean isValidSeed(String seed) { * @return true if the specified hashes are valid; otherwise, false. **/ public static boolean isHashes(List hashes) { + if (hashes == null || hashes.size() == 0) { + return false; + } for (String hash : hashes) { - if (!isTrytes(hash, 81)) { + if (!isHash(hash)) { return false; } } return true; } - + /** * Checks if input is correct hash. * @@ -227,9 +332,70 @@ public static boolean isHashes(List hashes) { * @return true if the specified hash are valid; otherwise, false. **/ public static boolean isHash(String hash) { - if (!isTrytes(hash, 81)) { + // Check if address with checksum + if (hash.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM) { + return isTrytesOfExactLength(hash, 0); //We already checked length + } else { + return isTrytesOfExactLength(hash, Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM); + } + } + + /** + * Checks if the uris are valid. + * + * @param uris The uris to validate. + * @return true if the specified uris are valid; otherwise, false. + **/ + public static boolean areValidUris(String... uris) { + if (uris == null || uris.length == 0) { return false; } + for (String uri : uris) { + if (!isValidUri(uri)) { + return false; + } + } + return true; + } + + /** + * Checks if the uri is valid. + * + * @param uri The uri to validate. + * @return true if the specified uri is valid; otherwise, false. + **/ + public static boolean isValidUri(String uri) { + if (uri.length() < 7) { + return false; + } + + String protocol = uri.substring(0, 6); + if (!"tcp://".equals(protocol) && !"udp://".equals(protocol)) { + return false; + } + + try { + new URI(uri); + return true; + } catch (URISyntaxException e) { + return false; + } + } + + /** + * Determines whether the specified string array contains only trytes of a transaction length + * @param trytes The trytes array to validate. + * @return true if the specified array contains only valid trytes otherwise, false. + **/ + public static boolean areTransactionTrytes(String... trytes) { + for (String tryteValue : trytes) { + + // Check if correct 2673 trytes + if (!isTrytesOfExactLength(tryteValue, Constants.TRANSACTION_SIZE)) { + return false; + } + } + return true; } @@ -244,11 +410,11 @@ public static boolean isArrayOfAttachedTrytes(String[] trytes) { for (String tryteValue : trytes) { // Check if correct 2673 trytes - if (!isTrytes(tryteValue, 2673)) { + if (!isTrytesOfExactLength(tryteValue, Constants.TRANSACTION_SIZE)) { return false; } - String lastTrytes = tryteValue.substring(2673 - (3 * 81)); + String lastTrytes = tryteValue.substring(Constants.TRANSACTION_SIZE - (3 * 81)); if (isNinesTrytes(lastTrytes, lastTrytes.length())) { return false; @@ -257,4 +423,13 @@ public static boolean isArrayOfAttachedTrytes(String[] trytes) { return true; } + + /** + * Checks if the security level is valid + * @param level the level + * @return true if the level is between 1 and 3(inclusive); otherwise, false. + */ + public static boolean isValidSecurityLevel(int level) { + return level >= Constants.MIN_SECURITY_LEVEL && level <= Constants.MAX_SECURITY_LEVEL; + } } diff --git a/jota/src/main/java/jota/utils/IotaAPIUtils.java b/jota/src/main/java/jota/utils/IotaAPIUtils.java index ac9b3a84..786d14de 100644 --- a/jota/src/main/java/jota/utils/IotaAPIUtils.java +++ b/jota/src/main/java/jota/utils/IotaAPIUtils.java @@ -16,7 +16,6 @@ /** * Client Side computation service. * - * @author davassi */ public class IotaAPIUtils { @@ -33,7 +32,7 @@ public class IotaAPIUtils { */ public static String newAddress(String seed, int security, int index, boolean checksum, ICurl curl) throws ArgumentException { - if (security < 1) { + if (!InputValidator.isValidSecurityLevel(security)) { throw new ArgumentException(INVALID_SECURITY_LEVEL_INPUT_ERROR); } diff --git a/jota/src/main/java/jota/utils/Signing.java b/jota/src/main/java/jota/utils/Signing.java index edd433ac..7c996227 100644 --- a/jota/src/main/java/jota/utils/Signing.java +++ b/jota/src/main/java/jota/utils/Signing.java @@ -9,43 +9,56 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import static jota.pow.JCurl.HASH_LENGTH; -import static jota.utils.Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR; + +import static jota.utils.Constants.*; public class Signing { - public final static int KEY_LENGTH = 6561; + private ICurl curl; + + public Signing() { + this(Optional.empty()); + } + + public Signing(ICurl curl) { + this(curl != null ? Optional.of(curl) : Optional.empty()); + } + /** - * public Signing() { - * this(null); - * } - * - * /** * * @param curl */ - public Signing(ICurl curl) { - this.curl = curl == null ? SpongeFactory.create(SpongeFactory.Mode.KERL) : curl; + public Signing(Optional curl) { + this.curl = curl == null || !curl.isPresent() ? SpongeFactory.create(SpongeFactory.Mode.KERL) : curl.get(); } - + /** - * @param inSeed - * @param index - * @param security - * @return - * @throws ArgumentException is thrown when the specified security level is not valid. + * Returns the sub-seed trits given a seed and an index + * @param inSeed the seed + * @param index the index + * @return the sub-seed */ - public int[] key(final int[] inSeed, final int index, int security) throws ArgumentException { - if (security < 1) { - throw new ArgumentException(INVALID_SECURITY_LEVEL_INPUT_ERROR); + public int[] subseed(int[] inSeed, int index) throws ArgumentException { + if (index < 0) { + throw new ArgumentException(INVALID_INDEX_INPUT_ERROR); } - + int[] seed = inSeed.clone(); + /* + * + * index 0 = [0,0,0,1,0,-1,0,1,1,0,-1,1,-1,0] + * index 1 = [1,0,0,1,0,-1,0,1,1,0,-1,1,-1,0] + * index 2 = [-1,1,0,1,0,-1,0,1,1,0,-1,1,-1,0] + * index 3 = [0,1,0,1,0,-1,0,1,1,0,-1,1,-1,0] + */ + // Derive subseed. for (int i = 0; i < index; i++) { for (int j = 0; j < seed.length; j++) { @@ -56,7 +69,43 @@ public int[] key(final int[] inSeed, final int index, int security) throws Argum } } } + return seed; + } + + /** + * Calculates security based on inSeed length / key length + * @param inSeed + * @param index + * @return + * @throws ArgumentException is thrown when the specified security level is not valid
+ * ArgumentException is thrown when inSeed length is not dividable by 3
+ * ArgumentException is thrown when index is below 1
+ */ + public int[] key(final int[] inSeed, int index) throws ArgumentException { + return key(inSeed, index, (int) Math.floor(inSeed.length / KEY_LENGTH)); + } + + /** + * + * @param inSeed + * @param index + * @param security + * @return + * @throws ArgumentException is thrown when the specified security level is not valid
+ * ArgumentException is thrown when inSeed length is not dividable by 3
+ * ArgumentException is thrown when index is below 1
+ */ + public int[] key(final int[] inSeed, final int index, int security) throws ArgumentException { + if (!InputValidator.isValidSecurityLevel(security)) { + throw new ArgumentException(INVALID_SECURITY_LEVEL_INPUT_ERROR); + } + + if (inSeed.length % 3 != 0) { + throw new ArgumentException(INVALID_SEED_INPUT_ERROR); + } + int[] seed = subseed(inSeed, index); + ICurl curl = this.getICurlObject(SpongeFactory.Mode.KERL); curl.reset(); curl.absorb(seed, 0, seed.length); @@ -81,21 +130,11 @@ public int[] key(final int[] inSeed, final int index, int security) throws Argum return key; } - public int[] signatureFragment(int[] normalizedBundleFragment, int[] keyFragment) { - int[] signatureFragment = keyFragment.clone(); - - for (int i = 0; i < 27; i++) { - - for (int j = 0; j < 13 - normalizedBundleFragment[i]; j++) { - curl.reset() - .absorb(signatureFragment, i * HASH_LENGTH, HASH_LENGTH) - .squeeze(signatureFragment, i * HASH_LENGTH, HASH_LENGTH); - } - } - - return signatureFragment; - } - + /** + * Address generates the address trits from the given digests. + * @param digests the digests + * @return the address trits + */ public int[] address(int[] digests) { int[] address = new int[HASH_LENGTH]; ICurl curl = this.getICurlObject(SpongeFactory.Mode.KERL); @@ -105,9 +144,18 @@ public int[] address(int[] digests) { return address; } - public int[] digests(int[] key) { + /** + * Digests hashes each segment of each key fragment 26 times and returns them. + * @param key the key trits + * @return the digests + * @throws ArgumentException if the security level is invalid + */ + public int[] digests(int[] key) throws ArgumentException { int security = (int) Math.floor(key.length / KEY_LENGTH); - + if (!InputValidator.isValidSecurityLevel(security)) { + throw new ArgumentException(INVALID_SECURITY_LEVEL_INPUT_ERROR); + } + int[] digests = new int[security * HASH_LENGTH]; int[] keyFragment = new int[KEY_LENGTH]; @@ -130,6 +178,12 @@ public int[] digests(int[] key) { return digests; } + /** + * + * @param normalizedBundleFragment + * @param signatureFragment + * @return + */ public int[] digest(int[] normalizedBundleFragment, int[] signatureFragment) { curl.reset(); ICurl jCurl = this.getICurlObject(SpongeFactory.Mode.KERL); @@ -149,6 +203,21 @@ public int[] digest(int[] normalizedBundleFragment, int[] signatureFragment) { return buffer; } + + public int[] signatureFragment(int[] normalizedBundleFragment, int[] keyFragment) { + int[] signatureFragment = keyFragment.clone(); + + for (int i = 0; i < 27; i++) { + + for (int j = 0; j < 13 - normalizedBundleFragment[i]; j++) { + curl.reset() + .absorb(signatureFragment, i * HASH_LENGTH, HASH_LENGTH) + .squeeze(signatureFragment, i * HASH_LENGTH, HASH_LENGTH); + } + } + + return signatureFragment; + } public Boolean validateSignatures(Bundle signedBundle, String inputAddress) { String bundleHash = ""; @@ -175,7 +244,6 @@ public Boolean validateSignatures(Bundle signedBundle, String inputAddress) { public Boolean validateSignatures(String expectedAddress, String[] signatureFragments, String bundleHash) { - Bundle bundle = new Bundle(); int[][] normalizedBundleFragments = new int[3][27]; @@ -203,5 +271,49 @@ public Boolean validateSignatures(String expectedAddress, String[] signatureFrag private ICurl getICurlObject(SpongeFactory.Mode mode) { return SpongeFactory.create(mode); } + + /** + * Normalizes the given bundle hash, with resulting digits summing to zero. + * It returns a slice with the tryte decimal representation without any 13/M values. + * @param bundleHash the bundle hash + * @return the normalized bundle hash in trits + */ + public int[] normalizedBundle(String bundleHash) { + int[] normalizedBundle = new int[81]; + + 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.tritsString("" + 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/jota/src/main/java/jota/utils/TrytesConverter.java b/jota/src/main/java/jota/utils/TrytesConverter.java index 8b90b75c..a0d370d4 100644 --- a/jota/src/main/java/jota/utils/TrytesConverter.java +++ b/jota/src/main/java/jota/utils/TrytesConverter.java @@ -8,7 +8,7 @@ public class TrytesConverter { /** - * Conversion of ascii encoded bytes to trytes. + * Conversion of ASCII encoded bytes to trytes. * Input is a string (can be stringified JSON object), return value is Trytes * * How the conversion works: @@ -37,7 +37,7 @@ public class TrytesConverter { * @param inputString The input String. * @return The ASCII char "Z" is represented as "IC" in trytes. */ - public static String toTrytes(String inputString) { + public static String asciiToTrytes(String inputString) { StringBuilder trytes = new StringBuilder(); @@ -62,15 +62,13 @@ public static String toTrytes(String inputString) { } /** - * Trytes to bytes - * Reverse operation from the byteToTrytes function in send.js + * Converts Trytes of even length to an ASCII string. + * Reverse operation from the asciiToTrytes * 2 Trytes == 1 Byte - * We assume that the trytes are a JSON encoded object thus for our encoding: - * First character = { - * Last character = } - * Everything after that is 9's padding + * @param inputTrytes the trytes we want to convert + * @return an ASCII string or null when the inputTrytes are uneven */ - public static String toString(String inputTrytes) { + public static String trytesToAscii(String inputTrytes) { // If input length is odd, return null if (inputTrytes.length() % 2 != 0) diff --git a/jota/src/test/java/jota/InputValidatorTest.java b/jota/src/test/java/jota/InputValidatorTest.java index ce47dce8..ba561acb 100644 --- a/jota/src/test/java/jota/InputValidatorTest.java +++ b/jota/src/test/java/jota/InputValidatorTest.java @@ -34,7 +34,7 @@ public void shouldCheckAddress() throws ArgumentException { @Test public void shouldIsTrytes() { - assertEquals(InputValidator.isTrytes(TEST_TRYTES, TEST_TRYTES.length()), true); + assertEquals(InputValidator.isTrytesOfExactLength(TEST_TRYTES, TEST_TRYTES.length()), true); } @Test diff --git a/jota/src/test/java/jota/IotaAPITest.java b/jota/src/test/java/jota/IotaAPITest.java index 9c4a5c9b..582c3aa0 100644 --- a/jota/src/test/java/jota/IotaAPITest.java +++ b/jota/src/test/java/jota/IotaAPITest.java @@ -28,7 +28,6 @@ /** * Let's do some integration test coverage against a default local real node. * - * @author davassi */ public class IotaAPITest { @@ -42,8 +41,8 @@ public class IotaAPITest { private static final String TEST_HASH = "AKK9DUFNOAAWCERFSUQTV9EOGFTBTSYYRQHEBQWRMDDBPYOS9UIEMQZEEVEQJZIWHKLRKCUJPOW9AC999"; private static final String TEST_INVALID_TRYTES = "BYSWEAUTWXHXZ9YBZISEK9LUHWGMHXCGEVNZHRLUWQFCUSDXZHOFHWHL9MQPVJXXZLIXPXPXF9KYEREFSKCPKYIIKPZVLHUTDFQKKVVBBN9ATTLPCNPJDWDEVIYYLGPZGCWXOBDXMLJC9VO9QXTTBLAXTTBFUAROYEGQIVB9MJWJKXJMCUPTWAUGFZBTZCSJVRBGMYXTVBDDS9MYUJCPZ9YDWWQNIPUAIJXXSNLKUBSCOIJPCLEFPOXFJREXQCUVUMKSDOVQGGHRNILCO9GNCLWFM9APMNMWYASHXQAYBEXF9QRIHIBHYEJOYHRQJAOKAQ9AJJFQ9WEIWIJOTZATIBOXQLBMIJU9PCGBLVDDVFP9CFFSXTDUXMEGOOFXWRTLFGV9XXMYWEMGQEEEDBTIJ9OJOXFAPFQXCDAXOUDMLVYRMRLUDBETOLRJQAEDDLNVIRQJUBZBO9CCFDHIX9MSQCWYAXJVWHCUPTRSXJDESISQPRKZAFKFRULCGVRSBLVFOPEYLEE99JD9SEBALQINPDAZHFAB9RNBH9AZWIJOTLBZVIEJIAYGMC9AZGNFWGRSWAXTYSXVROVNKCOQQIWGPNQZKHUNODGYADPYLZZZUQRTJRTODOUKAOITNOMWNGHJBBA99QUMBHRENGBHTH9KHUAOXBVIVDVYYZMSEYSJWIOGGXZVRGN999EEGQMCOYVJQRIRROMPCQBLDYIGQO9AMORPYFSSUGACOJXGAQSPDY9YWRRPESNXXBDQ9OZOXVIOMLGTSWAMKMTDRSPGJKGBXQIVNRJRFRYEZ9VJDLHIKPSKMYC9YEGHFDS9SGVDHRIXBEMLFIINOHVPXIFAZCJKBHVMQZEVWCOSNWQRDYWVAIBLSCBGESJUIBWZECPUCAYAWMTQKRMCHONIPKJYYTEGZCJYCT9ABRWTJLRQXKMWY9GWZMHYZNWPXULNZAPVQLPMYQZCYNEPOCGOHBJUZLZDPIXVHLDMQYJUUBEDXXPXFLNRGIPWBRNQQZJSGSJTTYHIGGFAWJVXWL9THTPWOOHTNQWCNYOYZXALHAZXVMIZE9WMQUDCHDJMIBWKTYH9AC9AFOT9DPCADCV9ZWUTE9QNOMSZPTZDJLJZCJGHXUNBJFUBJWQUEZDMHXGBPTNSPZBR9TGSKVOHMOQSWPGFLSWNESFKSAZY9HHERAXALZCABFYPOVLAHMIHVDBGKUMDXC9WHHTIRYHZVWNXSVQUWCR9M9RAGMFEZZKZ9XEOQGOSLFQCHHOKLDSA9QCMDGCGMRYJZLBVIFOLBIJPROKMHOYTBTJIWUZWJMCTKCJKKTR9LCVYPVJI9AHGI9JOWMIWZAGMLDFJA9WU9QAMEFGABIBEZNNAL9OXSBFLOEHKDGHWFQSHMPLYFCNXAAZYJLMQDEYRGL9QKCEUEJ9LLVUOINVSZZQHCIKPAGMT9CAYIIMTTBCPKWTYHOJIIY9GYNPAJNUJ9BKYYXSV9JSPEXYMCFAIKTGNRSQGUNIYZCRT9FOWENSZQPD9ALUPYYAVICHVYELYFPUYDTWUSWNIYFXPX9MICCCOOZIWRNJIDALWGWRATGLJXNAYTNIZWQ9YTVDBOFZRKO9CFWRPAQQRXTPACOWCPRLYRYSJARRKSQPR9TCFXDVIXLP9XVL99ERRDSOHBFJDJQQGGGCZNDQ9NYCTQJWVZIAELCRBJJFDMCNZU9FIZRPGNURTXOCDSQGXTQHKHUECGWFUUYS9J9NYQ9U9P9UUP9YMZHWWWCIASCFLCMSKTELZWUGCDE9YOKVOVKTAYPHDF9ZCCQAYPJIJNGSHUIHHCOSSOOBUDOKE9CJZGYSSGNCQJVBEFTZFJ9SQUHOASKRRGBSHWKBCBWBTJHOGQ9WOMQFHWJVEG9NYX9KWBTCAIXNXHEBDIOFO9ALYMFGRICLCKKLG9FOBOX9PDWNQRGHBKHGKKRLWTBEQMCWQRLHAVYYZDIIPKVQTHYTWQMTOACXZOQCDTJTBAAUWXSGJF9PNQIJ9AJRUMUVCPWYVYVARKR9RKGOUHHNKNVGGPDDLGKPQNOYHNKAVVKCXWXOQPZNSLATUJT9AUWRMPPSWHSTTYDFAQDXOCYTZHOYYGAIM9CELMZ9AZPWB9MJXGHOKDNNSZVUDAGXTJJSSZCPZVPZBYNNTUQABSXQWZCHDQSLGK9UOHCFKBIBNETK999999999999999999999999999999999999999999999999999999999999999999999999999999999NOXDXXKUDWLOFJLIPQIBRBMGDYCPGDNLQOLQS99EQYKBIU9VHCJVIPFUYCQDNY9APGEVYLCENJIOBLWNB999999999XKBRHUD99C99999999NKZKEKWLDKMJCI9N9XQOLWEPAYWSH9999999999999999999999999KDDTGZLIPBNZKMLTOLOXQVNGLASESDQVPTXALEKRMIOHQLUHD9ELQDBQETS9QFGTYOYWLNTSKKMVJAUXSIROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999IROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999"; private static final String TEST_TRYTES = "CCWCXCGDEAXCGDEAPCEAHDTCGDHDRAADTCGDGDPCVCTC9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999WITPXKWONPNUPBESJTXQTZTFXFSTDUWVGYHW9VRDULWGBKDVAAJLOSAEPUSCMSOIYLKMIZLPEKAOYAXMWFLDKQJI99999999999999999999UUC9999999999999999FUCK9YOUIVBUGXD99999999999B99999999IDPWGXASJFLLGCDGPQVXYGSNUESCZQCEKVREGLZX9FCYQVUWESEKWSMHZTGMJLGBOLKU9GILFITSJLZBWEHH9RSRNBPSKKUZBBJDSYHYWYHLJUOAFCKMXGTRFTZHKKWSVKGRHHJECSILLLZXKYJAYAQYEOINSZ9999OCYREQNINOB9XMLJOXMDFJDTXYO9PANNXQSW9HPFAAHEPTSPHTNEBOFFNRPKSUQNTKSACROOJPXF99999UUC9999999999999999FUCK9YOUPNMHCEJIE999999999L99999999IGMVBPROUATGVGQTHYIYFVQETRW"; - private static final String TEST_MESSAGE = "JUSTANOTHERJOTATEST"; - private static final String TEST_TAG = "JOTASPAM9999999999999999999"; + private static final String TEST_MESSAGE = "JUSTANOTHERIOTATEST"; + private static final String TEST_TAG = "IOTAJAVASPAM999999999999999"; private static final int MIN_WEIGHT_MAGNITUDE = 14; private static final int DEPTH = 9; @@ -128,31 +127,31 @@ public void shouldGetInputs() throws ArgumentException { @Test public void shouldCreateANewAddressWithChecksum() throws ArgumentException { - final GetNewAddressResponse res1 = iotaAPI.getNewAddress(TEST_SEED1, 1, 0, true, 5, false); + final GetNewAddressResponse res1 = iotaAPI.getAddressesUnchecked(TEST_SEED1, 1, true, 0, 5); assertThat(res1.getAddresses().get(0), Is.is(TEST_ADDRESS_WITH_CHECKSUM_SECURITY_LEVEL_1)); - final GetNewAddressResponse res2 = iotaAPI.getNewAddress(TEST_SEED1, 2, 0, true, 5, false); + final GetNewAddressResponse res2 = iotaAPI.getAddressesUnchecked(TEST_SEED1, 2, true, 0, 5); assertThat(res2.getAddresses().get(0), Is.is(TEST_ADDRESS_WITH_CHECKSUM_SECURITY_LEVEL_2)); - final GetNewAddressResponse res3 = iotaAPI.getNewAddress(TEST_SEED1, 3, 0, true, 5, false); + final GetNewAddressResponse res3 = iotaAPI.getAddressesUnchecked(TEST_SEED1, 3, true, 0, 5); assertThat(res3.getAddresses().get(0), Is.is(TEST_ADDRESS_WITH_CHECKSUM_SECURITY_LEVEL_3)); } @Test public void shouldCreateANewAddressWithoutChecksum() throws ArgumentException { - final GetNewAddressResponse res1 = iotaAPI.getNewAddress(TEST_SEED1, 1, 0, false, 5, false); + final GetNewAddressResponse res1 = iotaAPI.getAddressesUnchecked(TEST_SEED1, 1, false, 0, 5); assertThat(res1.getAddresses().get(0), Is.is(TEST_ADDRESS_WITHOUT_CHECKSUM_SECURITY_LEVEL_1)); - final GetNewAddressResponse res2 = iotaAPI.getNewAddress(TEST_SEED1, 2, 0, false, 5, false); + final GetNewAddressResponse res2 = iotaAPI.getAddressesUnchecked(TEST_SEED1, 2, false, 0, 5); assertThat(res2.getAddresses().get(0), Is.is(TEST_ADDRESS_WITHOUT_CHECKSUM_SECURITY_LEVEL_2)); - final GetNewAddressResponse res3 = iotaAPI.getNewAddress(TEST_SEED1, 3, 0, false, 5, false); + final GetNewAddressResponse res3 = iotaAPI.getAddressesUnchecked(TEST_SEED1, 3, false, 0, 5); assertThat(res3.getAddresses().get(0), Is.is(TEST_ADDRESS_WITHOUT_CHECKSUM_SECURITY_LEVEL_3)); } @Test public void shouldCreate100Addresses() throws ArgumentException { - GetNewAddressResponse res = iotaAPI.getNewAddress(TEST_SEED1, 2, 0, false, 100, false); + GetNewAddressResponse res = iotaAPI.getAddressesUnchecked(TEST_SEED1, 2, false, 0, 100); assertEquals(res.getAddresses().size(), 100); } @@ -270,9 +269,9 @@ public void shouldBroadcastAndStore() throws ArgumentException { @Category(IntegrationTest.class) public void shouldFailBeforeSnapshotTimeStamp() throws ArgumentException { try { - iotaAPI.broadcastAndStore(TEST_TRYTES); + iotaAPI.storeAndBroadcast(TEST_TRYTES); fail("Transaction did not fail on old timestamp value"); - } catch (IllegalAccessError e) { + } catch (ArgumentException e) { //TODO Check for specific error } } diff --git a/jota/src/test/java/jota/IotaLocalPoWTest.java b/jota/src/test/java/jota/IotaLocalPoWTest.java index 34890ba5..51dfdcd2 100644 --- a/jota/src/test/java/jota/IotaLocalPoWTest.java +++ b/jota/src/test/java/jota/IotaLocalPoWTest.java @@ -1,9 +1,10 @@ package jota; -import cfb.pearldiver.PearlDiverLocalPoW; import jota.dto.response.SendTransferResponse; import jota.error.ArgumentException; import jota.model.Transfer; +import jota.pow.pearldiver.PearlDiverLocalPoW; + import org.hamcrest.core.IsNull; import org.junit.Before; import org.junit.Ignore; diff --git a/jota/src/test/java/jota/SigningTest.java b/jota/src/test/java/jota/SigningTest.java index 21eca600..18db5d1f 100644 --- a/jota/src/test/java/jota/SigningTest.java +++ b/jota/src/test/java/jota/SigningTest.java @@ -52,13 +52,13 @@ public void testLongSeedKeyGeneration() throws ArgumentException { Signing signing = new Signing(curl); String seed = "EV9QRJFJZVFNLYUFXWKXMCRRPNAZYQVEYB9VEPUHQNXJCWKZFVUCTQJFCUAMXAHMMIUQUJDG9UGGQBPIY"; - for(int i = 1; i < 5; i++) { + for(int i = Constants.MIN_SECURITY_LEVEL; i < Constants.MAX_SECURITY_LEVEL; i++) { int[] key1 = signing.key(Converter.trits(seed), 0, i); - assertEquals(Signing.KEY_LENGTH * i, key1.length); + assertEquals(Constants.KEY_LENGTH * i, key1.length); int[] key2 = signing.key(Converter.trits(seed + seed), 0, i); - assertEquals(Signing.KEY_LENGTH * i, key2.length ); + assertEquals(Constants.KEY_LENGTH * i, key2.length ); int[] key3 = signing.key(Converter.trits(seed + seed + seed), 0, i); - assertEquals(Signing.KEY_LENGTH * i, key3.length ); + assertEquals(Constants.KEY_LENGTH * i, key3.length ); } } @@ -68,7 +68,7 @@ public void testSigning() throws ArgumentException { // address of our test seed // (but remove the checksum) with the key of our fifth address String hashToSign = removeChecksum(FIRST_ADDR); - Signing signing = new Signing(null); + Signing signing = new Signing(); final int[] key = signing.key(Converter.trits(TEST_SEED), 5, 2); int[] normalizedHash = new Bundle().normalizedBundle(hashToSign); int[] signature = signing.signatureFragment(Arrays.copyOfRange(normalizedHash, 0, 27), Arrays.copyOfRange(key, 0, 6561)); @@ -79,18 +79,18 @@ public void testSigning() throws ArgumentException { @Test public void testKeyLength() throws ArgumentException { - Signing signing = new Signing(null); + Signing signing = new Signing(); int[] key = signing.key(Converter.trits(TEST_SEED), 5, 1); - assertEquals(Signing.KEY_LENGTH, key.length); + assertEquals(Constants.KEY_LENGTH, key.length); key = signing.key(Converter.trits(TEST_SEED), 5, 2); - assertEquals(2 * Signing.KEY_LENGTH, key.length); + assertEquals(2 * Constants.KEY_LENGTH, key.length); key = signing.key(Converter.trits(TEST_SEED), 5, 3); - assertEquals(3 * Signing.KEY_LENGTH, key.length); + assertEquals(3 * Constants.KEY_LENGTH, key.length); } @Test public void testVerifying() throws ArgumentException { - assertTrue(new Signing(null).validateSignatures(removeChecksum(SIXTH_ADDR), new String[]{SIGNATURE1, SIGNATURE2}, removeChecksum(FIRST_ADDR))); + assertTrue(new Signing().validateSignatures(removeChecksum(SIXTH_ADDR), new String[]{SIGNATURE1, SIGNATURE2}, removeChecksum(FIRST_ADDR))); } private String removeChecksum(String address) throws ArgumentException { diff --git a/jota/src/test/java/jota/TrytesConverterTest.java b/jota/src/test/java/jota/TrytesConverterTest.java index 1b129732..c0740397 100644 --- a/jota/src/test/java/jota/TrytesConverterTest.java +++ b/jota/src/test/java/jota/TrytesConverterTest.java @@ -14,20 +14,20 @@ public class TrytesConverterTest { @Test public void shouldConvertStringToTrytes() { - assertEquals(TrytesConverter.toTrytes("Z"), "IC"); - assertEquals(TrytesConverter.toTrytes("JOTA JOTA"), "TBYBCCKBEATBYBCCKB"); + assertEquals(TrytesConverter.asciiToTrytes("Z"), "IC"); + assertEquals(TrytesConverter.asciiToTrytes("JOTA JOTA"), "TBYBCCKBEATBYBCCKB"); } @Test public void shouldConvertTrytesToString() { - assertEquals(TrytesConverter.toString("IC"), "Z"); - assertEquals(TrytesConverter.toString("TBYBCCKBEATBYBCCKB"), "JOTA JOTA"); + assertEquals(TrytesConverter.trytesToAscii("IC"), "Z"); + assertEquals(TrytesConverter.trytesToAscii("TBYBCCKBEATBYBCCKB"), "JOTA JOTA"); } @Test public void shouldConvertBackAndForth() { String str = RandomStringUtils.randomAlphabetic(1000).toUpperCase(); - String back = TrytesConverter.toString(TrytesConverter.toTrytes(str)); + String back = TrytesConverter.trytesToAscii(TrytesConverter.asciiToTrytes(str)); assertTrue(str.equals(back)); } } \ No newline at end of file diff --git a/pom.xml b/pom.xml index 10fa9ee5..60c7e682 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.iota jota-parent - 1.0.0-beta1 + 1.0.0-beta2 pom JOTA JOTA library is a simple Java wrapper around IOTA Node's JSON-REST HTTP interface. diff --git a/ruleset.xml b/ruleset.xml new file mode 100644 index 00000000..eb0f082b --- /dev/null +++ b/ruleset.xml @@ -0,0 +1,96 @@ + + + + + The Iota ruleset contains a collection of good practices which should be followed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +