From f694178ac1ddd26b1671753225a72fedfc32b12e Mon Sep 17 00:00:00 2001 From: Michael Schierl Date: Sat, 2 Sep 2017 21:27:45 +0200 Subject: [PATCH 1/4] Use correct arraycopy length The code copies 243 single adjacent element in an array using arraycopy in a loop. For copying single elements, direct array access is more performant (as it avoids the penalty of calling into native code and the JIT may detect patterns and optimize them). However, in this case, as the copied elements are adjacent, replace the whole loop with a single arraycopy call. --- src/main/java/jota/utils/Signing.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/jota/utils/Signing.java b/src/main/java/jota/utils/Signing.java index a1e3cdf..9119f3f 100644 --- a/src/main/java/jota/utils/Signing.java +++ b/src/main/java/jota/utils/Signing.java @@ -93,9 +93,7 @@ public class Signing { .squeeze(hash, 0, hash.length); } - for (int j = 0; j < 243; j++) { - System.arraycopy(hash, j, keyFragment, i * 243 + j, 1); - } + System.arraycopy(hash, 0, keyFragment, i * 243, 243); } return keyFragment; @@ -202,9 +200,7 @@ public class Signing { int[] digestBuffer = digest(normalizedBundleFragments[i % 3], Converter.trits(signatureFragments[i])); - for (int j = 0; j < 243; j++) { - System.arraycopy(digestBuffer, j, digests, i * 243 + j, 1); - } + System.arraycopy(digestBuffer, 0, digests, i * 243, 243); } String address = Converter.trytes(address(digests)); From 53df67d4908152b6edaa7a1643d7f24598360e78 Mon Sep 17 00:00:00 2001 From: Michael Schierl Date: Thu, 7 Sep 2017 22:01:31 +0200 Subject: [PATCH 2/4] More arraycopy improvements In fact, most arraycopy and copyOfRange calls in the class are unneeded, since Kerl can also directly operate on array slices. Care has been taken not to introduce new side effects, therefore one copyOfRange call in `digests` needs to stay. Existing side effects are kept, although probably undesirable (`signatureFragment` clobbers the `keyFragment` input parameter). --- src/main/java/jota/utils/Signing.java | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/main/java/jota/utils/Signing.java b/src/main/java/jota/utils/Signing.java index 9119f3f..e03543c 100644 --- a/src/main/java/jota/utils/Signing.java +++ b/src/main/java/jota/utils/Signing.java @@ -81,19 +81,12 @@ public class Signing { public int[] signatureFragment(int[] normalizedBundleFragment, int[] keyFragment) { - int[] hash; - for (int i = 0; i < 27; i++) { - - hash = Arrays.copyOfRange(keyFragment, 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); + .absorb(keyFragment, i * 243, 243) + .squeeze(keyFragment, i * 243, 243); } - - System.arraycopy(hash, 0, keyFragment, i * 243, 243); } return keyFragment; @@ -110,41 +103,34 @@ public class Signing { public int[] digests(int[] key) { 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); + .absorb(keyFragment, j * 243, 243) + .squeeze(keyFragment, j * 243, 243); } - 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); + curl.squeeze(digests, i * 243, 243); } return digests; } public int[] digest(int[] normalizedBundleFragment, int[] signatureFragment) { curl.reset(); + ICurl jCurl = SpongeFactory.create(SpongeFactory.Mode.KERL); int[] buffer = new int[243]; for (int i = 0; i < 27; i++) { buffer = Arrays.copyOfRange(signatureFragment, i * 243, (i + 1) * 243); for (int j = normalizedBundleFragment[i] + 13; j-- > 0; ) { - - ICurl jCurl = SpongeFactory.create(SpongeFactory.Mode.KERL); jCurl.reset(); jCurl.absorb(buffer); jCurl.squeeze(buffer); From c4535aefbea7b22f5f9abe894527fc10962e17c0 Mon Sep 17 00:00:00 2001 From: Michael Schierl Date: Thu, 7 Sep 2017 22:13:26 +0200 Subject: [PATCH 3/4] Avoid copying from Array to list and back As Java's Lists use boxed Integers, we try to avoid the memory consumption, GC pressure and time overhead caused by copying an int[] into a List and back again. --- src/main/java/jota/utils/Signing.java | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/main/java/jota/utils/Signing.java b/src/main/java/jota/utils/Signing.java index e03543c..19cba57 100644 --- a/src/main/java/jota/utils/Signing.java +++ b/src/main/java/jota/utils/Signing.java @@ -54,29 +54,16 @@ public class Signing { curl.reset(); curl.absorb(seed, 0, seed.length); - final List key = new ArrayList<>(); - int[] buffer = new int[seed.length]; + final int[] key = new int[length * seed.length * 27]; int offset = 0; 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]); - } + curl.squeeze(key, offset, seed.length); + offset += seed.length; } } - return to(key); - } - - private int[] to(List key) { - int a[] = new int[key.size()]; - int i = 0; - for (Integer v : key) { - a[i++] = v; - } - return a; + return key; } public int[] signatureFragment(int[] normalizedBundleFragment, int[] keyFragment) { From 73c3ae2bd4e8fc9816747a832d120acd57d5b15f Mon Sep 17 00:00:00 2001 From: Michael Schierl Date: Thu, 7 Sep 2017 22:17:14 +0200 Subject: [PATCH 4/4] Add unit tests for Signing class All methods that I changed in previous commits of this PR are covered, and the tests path both before and after my refactorings. --- src/test/java/jota/SigningTest.java | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/test/java/jota/SigningTest.java diff --git a/src/test/java/jota/SigningTest.java b/src/test/java/jota/SigningTest.java new file mode 100644 index 0000000..1b4a5a6 --- /dev/null +++ b/src/test/java/jota/SigningTest.java @@ -0,0 +1,61 @@ +package jota; + +import jota.error.InvalidAddressException; +import jota.model.Bundle; +import jota.utils.Checksum; +import jota.utils.Constants; +import jota.utils.Converter; +import jota.utils.InputValidator; +import jota.utils.IotaAPIUtils; +import jota.utils.SeedRandomGenerator; +import jota.utils.Signing; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +/** + * @author schierlm@gmx.de + */ +public class SigningTest { + + private static final String TEST_SEED = "IHDEENZYITYVYSPKAURUZAQKGVJEREFDJMYTANNXXGPZ9GJWTEOJJ9IPMXOGZNQLSNMFDSQOTZAEETUEA"; + private static final String FIRST_ADDR = "LXQHWNY9CQOHPNMKFJFIJHGEPAENAOVFRDIBF99PPHDTWJDCGHLYETXT9NPUVSNKT9XDTDYNJKJCPQMZCCOZVXMTXC"; + private static final String SIXTH_ADDR = "HLHRSJNPUUGRYOVYPSTEQJKETXNXDIWQURLTYDBJADGIYZCFXZTTFSOCECPPPPY9BYWPODZOCWJKXEWXDPUYEOTFQA"; + private static final String SIGNATURE1 = "PYWFM9MYTPNZ9HTLZBBB9CGQWKPALDUNAQYCAA9VMQ9UMBLLAXSPPHQSNAAKJA9MZBXBHBQBFFKMBSDHDTCVCDWLUYCEQ9YZJAJAXXXZHDWTSLWGIWRE9LJFVWAFUMOAGHDBHJQ9APNBLSX9GPTJNTO9SBJT9UKYCZXYAWVGXEBJANNWEWZSPRYHASHGIFUWOEHUFMP9MWQBYZOZESCPLVJUCWGLEJIDPMEVNPBITBNFSQ9GBWCDTQZOPLPXOWWNQAEIXQRWMHAQDH9C9KKHGNKAX9INMUVVGIK9TPGRHOMDFAB9VICYDMSHHDDBRSTEFSZXMXFJUQRRAFBSCNHSMKRNNTTCMBURKBGC9EDWKLPBSQAKYCUKKSZWRVURZGUA9QVSXXPICIYFHLPJSWEFBZPUTWWNIKSAJM9OMRFFQVFJZZHLQBSEYXM9CN9HCGHSJBTYDGWOQPXOPZZE9EPQAQFT9GDWZCSOPMZHYYZXDDZ9DJDLOOOTIFQANFANNAYVIRUNDXSB9XRNXJYRDBLTEDWSUOVISMCHGKD9KDRSFDWRSVZQQKGAMDXFAWBSLMTTUMH9RAUIVI9HJMTODACSOP9MLHOJMSIWQ9TTNGPXRNWRHLMEMAH9GZHJRNJHQNBBLWKFXIZBMGMATZIZBFDPAFDCLDIFFAIK9JUSFYYC9ANDGXCZFLZYGURTUI9SWYYRGDJAHXDDNHSJZBCENZUSQXSFZMTXSFLRK9RIYAUMHPBOBNOXCHDIMBGIBVOOHIDQ9ORHHDECDTREIEILWDUFMUWYMGIXBIKRZMKGXTYZTX9GKFP9AUXMTUUQXRHHKPYULGJFJLEEYCNKLOWULRIAFM9OYKEDFRXFVTSJMSEMOURCLNOIETIHEUCMPLWKDXDO9TAHVH99MKTBAAKCMYKLJUQIVLLSVTFUM9KDSIHYXYHPRLDADSLSSOIGLLXMPKTHS9YXUNMUTBTBPDWXA9GVTBGLTCLEZEUNNIRBBURDWOFFYXELPFSZRQARVRPHGETKJTRUZIFDDWBOHHGUZTODZFMOVMAGCYCTGBWSGAVZADIPIASCKTRKIUUMHNGUYZKDVOPKKHXD9EXVUVJ9YFNYMLIJLEEGPIZLFS9FIEMG9MIEO9FPW9JZEVDQOECMTESICSMVWXZNXXJILJLVQHEBHQWPOBHKEGRLFCPLB9ZECJOZDAB9DMU9UALBIQDABVDYRRTPMZOCQX9WNGXVNKQZWPA9ACVONQMRHQDPPIQTP9VKP9PAORNOFTZZWGC9RYBWSNLULZGYLMYIWWPDMOHPZTQWRPRCN9RAUOKDSCWBRI9NPUPLBILOZDOOPHSWQGJEGUYWAWJDEBLEOBSYYU9XSRPBHRUQXIDOWJZQQVJTMP9VLWLOGBK9FZFHYLJCNENDATNPSF99DFPVPTNNKIUMHRGEBJXNUVENAHYLFPPHYFTIKCB9DBVCCSJTDMOMISBAAEJVBVLHOADKNFG9NQGIGRDICQCWZVHGGXLTUNQKBUTLDWXIM9REWBLIXFBPTOXBLWBQQUSRLRDHTXQWARPMBQILAJSYLLTDAGTFPCXBCDITDOIZNGKPZQWWHJDZIPYCPFEYFD9CVXYOJHJNUNMCMSIAUVSKCACNNPGDYJJVTZOREJOPIBYCMBULMTSDTJPZNVNYQBQPPABOSSNZJKQQZ9LULSHJUBLHIFMYWSNPGUERCLVFV9LOEBJEERYHI9OMSMSCDFDLNHEMLQXNRJDYSNKTOYCPTAUWAWIGCPJKMAMGLXNBJMO9BZGFIHWDVJWYCNZZV9KBWIFQSMAXBPGVXDW9SLTHOLMJORRXZJSTNOQDRGNBLGTFCCNBJECYZGWTDRJKJRBAJRCULMOUBQJFWCLWMEWGAAVNZWMDWBYDKZMUCZAKXQLRQPIQJPMORKJXKSDTGXWDHAKUOSMXCFXWSZYWXODWFACBMFSWQFVMBELPZMISVWRQQQPNHOTWOEQQAQJDLXFEEBXLJQEECWG9ARRRDLTVBHTPARJMLOZHYWDCSXPTZCNZWTCRUJNZWKFZXAARPHFCBTLWSLERGJJMKIG9NEBADRMZWYNWIRGTMOBRKURUE9GDLRIEODY9BXJOZUVNCXKXFPFDXKUTMXZRJDOQ9YTV9BJDKGZBYTWGVPQQMNVCNARLPSRQWN9TRMHWLNEJZFTCSRD"; + private static final String SIGNATURE2 = "URKFKLNXFEKDOGSQVMAOPEDIWSMTCKJZ9KEVWYALY9JAO9KHUGNDTMGQLKQJUIPWDIVMPEDSVPLFMDCIXDDT9WBBRTFQENL9AXLSBYHINXCDYBFGRNKJDYHAQVJKWCVOYXHTNBEZUNLVMJLUMZYJFAOW9PVVMJZNZZFJQEQFELVFZVFVWPJ9WQZJLPSGBYECHXSFVFQJGUCPFXC9GATTILVCAANNHOYMLOYX9QSUPCERYCOXPACZEEGLREBRZWXGUTTVTHB9GBRCIFEOBPIRXXPQKRSODEHDSZXLGIKXUQWNTQKIOPVDVSIK9WJUAEFOJBU9MBPBSVYSCLBMINTT9ZCTREZSMSVOPXSZOMCGFEZKMOCNLJ9QUTAPKBHRIAIYLCHUQHOINKSCMXWZVDGDXHNJQXJHPCCGBEWROVKEPAPBFFRCAVXZWIRKCRAWYHIHMDXFAGDJQNJJPYSQUHKFOOCEVQOGRQEIOQFKZWUQ9XVRNXKGMJOQEZHQZXQABWUQRBKXWHYUXEAEMDGXVY9WS9VJOCMGBQASSRNKAYJPTSPQEMYSJMTCLMDQJKDPBGQZZSFBDOKHBYY9UDRXNKTPWBCQTVKUGMEDUXL9TTKPATNIKVAGHACHPFSCRYNIRJBQC9OADPGWBFYYARSVNQCGMYQGCYLZH9KLMUIJPCLPQVS9BORXCJBXPDECJGKDNOUYWTKKFLXZARWKGUSMVMXKJTMRYZRERFCFGTZFZFCAOQSZGPQJUEZUJLJPU9QPMJUTZNLMSMPRGIFHUUZHMPMRBEBATEIIWPCOIMWOYOG9NYFBYOWFDKRXOTREBU99GNCPXKOWGI99LNVPRFFF9FCLFXI9HMUFU9NRLNJVTFNUSUJTAVOG9GKUYYEXIM9HTPIDTWIGLKRAQPKMQVZAPYMPSQIOJ9JZBWDMQHDSSRSHNCWSAJCSRORSEXLLQNZUKPXPGRLYMXOXWCCWWSBALFLXPHSGFLTOAFWPETBKJUMBLHMSKYLPJT9EJAZCPPNZWKPVCGKDJCRCLBBIAKVDSNWGONPLKFAYXZDI9FKPHDPKCB9UUPXLJVQTXOAZOQDRNSONXDVSLQGZYRIPGREYHRAUOSBFZDZPZHFNMWCZQGPXCZVLNCSASB9RQDFHOYMUVYLFKOEEWNREYCDMCTZIAFBFKLKRQWZCJHQZCZGWXIFTKRVMPHMVHAABHBDEV9WDEZBR9FLXLNBVNYKUOUFJQKNZVZVGZDDTFYNYFUVRLZKOLXXQYNV9MDVBLZSERXPGYKRIEZQZD9IBKFDT9AIYGWJJCXFWDUDURGJQLXVEJAVEOMZUVVTNCVBXEVQRDQIEHDUCSLCIJUTSCLFXEGMFYP9YLXELCZPMTBZWBIODZCFNJLVWTPQGLMQIHIABAYGJFFMOEDTCXGEDTNXMVXZYFGXRKVVRTIZ9ISXTDHAFPEKQZSM9XXQLOYBLTMD9MBERBIBEJDEXGMOLDZPZVVEPIRKJBDPAKFAWJPTCJSHZPDUKZEEHRFLMZCUGCOWFJBSTDGPHUIXSPPPHRQARMCFMTWKYPJNJQV9VSFZ9EWB9GVEAFUXHWRNUXQLCSBWROOITBATWUXUYGSMGAXKGEBP9ZJWXQWHBVPOSLDHTWXUOFQNO9EXSYPQF9LQLQAFNRU9MTIIRQLBBBYKUPANWRQKGESFARQIRUTGFMZVUKHZJYKTYOARTDOBIYBFRHJWEFHCYVHRHTLTWBRMUDVIVQVNELQMQRXYDNGVSICZINWIZCIWVFXLYOLYKWDNWCWFZUXHUWOPRDHMTSXOZX9CVHANU9ZXTJOGKEPYR9CHGOTIUQSWIALAOIKHQFXWY9ZWTSZADVXJNNZOLSCXVVFBRHLRBTGMSZOYNIXTAMABKGJTLGTZKRHOPPJMNYIQNVKRGXUQDWYEIEZYM9CSXO9YLSBJLDJUWOLUXDEKBGGEIDEXFLZMESDOITNYTNRLGOMHJH9HOLXJABUNLXCZYTXFPZMHRJPLXSVPDBJBBZX9TBIMZZFZOXUSFEJYHEXPFXGJCQTBBLPEEWAPHUETGXSXYYAF9PCCCOONRMQGAPJ9JO9BZQ9QSKTPFFYIFVHSLAZY9CWYSIMKDOSLRKWBHPGJGVEJEEMLCCWXKSOCMBMZZZJWYBBXE9FTAYJALGWITJRXAXWZEXMECTZEEIWZPHYX"; + + @Test + public void testAddressGeneration() throws InvalidAddressException { + assertEquals(FIRST_ADDR, IotaAPIUtils.newAddress(TEST_SEED, 2, 0, true, null)); + assertEquals(SIXTH_ADDR, IotaAPIUtils.newAddress(TEST_SEED, 2, 5, true, null)); + } + + @Test + public void testSigning() throws InvalidAddressException { + // we can sign any hash, so for convenience we will sign the first + // 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); + 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)); + assertEquals(SIGNATURE1, Converter.trytes(signature)); + int[] signature2 = signing.signatureFragment(Arrays.copyOfRange(normalizedHash, 27, 27 * 2), Arrays.copyOfRange(key, 6561, 6561 * 2)); + assertEquals(SIGNATURE2, Converter.trytes(signature2)); + } + + @Test + public void testVerifying() throws InvalidAddressException { + assertTrue(new Signing(null).validateSignatures(removeChecksum(SIXTH_ADDR), new String[] { SIGNATURE1, SIGNATURE2 }, removeChecksum(FIRST_ADDR))); + } + + private String removeChecksum(String address) throws InvalidAddressException { + assertTrue(Checksum.isValidChecksum(address)); + return address.substring(0, Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM); + } +}