diff --git a/jota/src/main/java/jota/IotaAPICore.java b/jota/src/main/java/jota/IotaAPICore.java index 96651c5..3aa82a9 100644 --- a/jota/src/main/java/jota/IotaAPICore.java +++ b/jota/src/main/java/jota/IotaAPICore.java @@ -313,7 +313,7 @@ public class IotaAPICore { nodeConfig = new Properties(); if (null == fileReader) { try { - fileReader = new FileReader("node_config.properties"); + fileReader = new FileReader("../node_config.properties"); if (null == bufferedReader) { bufferedReader = new BufferedReader(fileReader); diff --git a/jota/src/main/java/jota/utils/IotaAPIUtils.java b/jota/src/main/java/jota/utils/IotaAPIUtils.java index 6ab7830..4d17712 100644 --- a/jota/src/main/java/jota/utils/IotaAPIUtils.java +++ b/jota/src/main/java/jota/utils/IotaAPIUtils.java @@ -1,6 +1,7 @@ package jota.utils; import jota.error.InvalidAddressException; +import jota.error.InvalidSecurityLevelException; import jota.model.Bundle; import jota.model.Input; import jota.model.Transaction; @@ -28,8 +29,9 @@ public class IotaAPIUtils { * @param curl The curl instance. * @return An String with address. * @throws InvalidAddressException is thrown when the specified address is not an valid address. + * @throws InvalidSecurityLevelException is thrown when the specified security level is not valid. */ - public static String newAddress(String seed, int security, int index, boolean checksum, ICurl curl) throws InvalidAddressException { + public static String newAddress(String seed, int security, int index, boolean checksum, ICurl curl) throws InvalidAddressException, InvalidSecurityLevelException { Signing signing = new Signing(curl); final int[] key = signing.key(Converter.trits(seed), index, security); final int[] digests = signing.digests(key); @@ -46,7 +48,7 @@ public class IotaAPIUtils { public static List signInputsAndReturn(final String seed, final List inputs, final Bundle bundle, - final List signatureFragments, ICurl curl) { + final List signatureFragments, ICurl curl) throws InvalidSecurityLevelException { bundle.finalize(curl); bundle.addTrytes(signatureFragments); diff --git a/jota/src/main/java/jota/utils/Multisig.java b/jota/src/main/java/jota/utils/Multisig.java index 7a185d2..b08dd19 100644 --- a/jota/src/main/java/jota/utils/Multisig.java +++ b/jota/src/main/java/jota/utils/Multisig.java @@ -1,5 +1,6 @@ package jota.utils; +import jota.error.InvalidSecurityLevelException; import jota.model.Bundle; import jota.pow.ICurl; import jota.pow.SpongeFactory; @@ -36,7 +37,7 @@ public class Multisig { * @param index Key index to start search from. If the index is provided, the generation of the address is not deterministic. * @return trytes **/ - public String getDigest(String seed, int security, int index) { + public String getDigest(String seed, int security, int index) throws InvalidSecurityLevelException { int[] key = signingInstance.key(Converter.trits(seed, 243), index, security); return Converter.trytes(signingInstance.digests(key)); } @@ -75,7 +76,7 @@ public class Multisig { * @return trytes. **/ - public String getKey(String seed, int index, int security) { + public String getKey(String seed, int index, int security) throws InvalidSecurityLevelException { return Converter.trytes(signingInstance.key(Converter.trits(seed, 81 * security), index, security)); } diff --git a/jota/src/main/java/jota/utils/Signing.java b/jota/src/main/java/jota/utils/Signing.java index 19cba57..0519e41 100644 --- a/jota/src/main/java/jota/utils/Signing.java +++ b/jota/src/main/java/jota/utils/Signing.java @@ -1,5 +1,6 @@ package jota.utils; +import jota.error.InvalidSecurityLevelException; import jota.model.Bundle; import jota.model.Transaction; import jota.pow.ICurl; @@ -9,12 +10,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static jota.pow.JCurl.HASH_LENGTH; + public class Signing { + public final static int KEY_LENGTH = 6561; - /** - * Ask @come-from-beyond - */ private ICurl curl; /** @@ -31,15 +32,21 @@ public class Signing { } /** - * @param seed + * @param inSeed * @param index - * @param length + * @param security * @return */ - public int[] key(int[] seed, int index, int length) { + public int[] key(final int[] inSeed, final int index, int security) throws InvalidSecurityLevelException { + if (security < 1 || security > 3) { + throw new InvalidSecurityLevelException(); + } + int[] seed = inSeed.clone(); + + // Derive subseed. for (int i = 0; i < index; i++) { - for (int j = 0; j < 243; j++) { + for (int j = 0; j < HASH_LENGTH; j++) { if (++seed[j] > 1) { seed[j] = -1; } else { @@ -50,16 +57,18 @@ public class Signing { curl.reset(); curl.absorb(seed, 0, seed.length); - curl.squeeze(seed, 0, seed.length); + // seed[0..HASH_LENGTH] contains subseed + curl.squeeze(seed, 0, HASH_LENGTH); curl.reset(); - curl.absorb(seed, 0, seed.length); + // absorb subseed + curl.absorb(seed, 0, HASH_LENGTH); - final int[] key = new int[length * seed.length * 27]; + final int[] key = new int[security * HASH_LENGTH * 27]; int offset = 0; - while (length-- > 0) { + while (security-- > 0) { for (int i = 0; i < 27; i++) { - curl.squeeze(key, offset, seed.length); + curl.squeeze(key, offset, HASH_LENGTH); offset += seed.length; } } @@ -67,20 +76,22 @@ public class Signing { } 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(keyFragment, i * 243, 243) - .squeeze(keyFragment, i * 243, 243); + .absorb(signatureFragment, i * HASH_LENGTH, HASH_LENGTH) + .squeeze(signatureFragment, i * HASH_LENGTH, HASH_LENGTH); } } - return keyFragment; + return signatureFragment; } public int[] address(int[] digests) { - int[] address = new int[243]; + int[] address = new int[HASH_LENGTH]; curl.reset() .absorb(digests) .squeeze(address); @@ -88,23 +99,25 @@ public class Signing { } public int[] digests(int[] key) { + int security = (int) Math.floor(key.length / KEY_LENGTH); - int[] digests = new int[(int) Math.floor(key.length / 6561) * 243]; + int[] digests = new int[security * HASH_LENGTH]; + int[] keyFragment = new int[KEY_LENGTH]; - for (int i = 0; i < Math.floor(key.length / 6561); i++) { - int[] keyFragment = Arrays.copyOfRange(key, i * 6561, (i + 1) * 6561); + for (int i = 0; i < Math.floor(key.length / KEY_LENGTH); i++) { + System.arraycopy(key, i * KEY_LENGTH, keyFragment, 0, KEY_LENGTH); for (int j = 0; j < 27; j++) { for (int k = 0; k < 26; k++) { curl.reset() - .absorb(keyFragment, j * 243, 243) - .squeeze(keyFragment, j * 243, 243); + .absorb(keyFragment, j * HASH_LENGTH, HASH_LENGTH) + .squeeze(keyFragment, j * HASH_LENGTH, HASH_LENGTH); } } curl.reset(); curl.absorb(keyFragment, 0, keyFragment.length); - curl.squeeze(digests, i * 243, 243); + curl.squeeze(digests, i * HASH_LENGTH, HASH_LENGTH); } return digests; } @@ -112,10 +125,10 @@ public class Signing { public int[] digest(int[] normalizedBundleFragment, int[] signatureFragment) { curl.reset(); ICurl jCurl = SpongeFactory.create(SpongeFactory.Mode.KERL); - int[] buffer = new int[243]; + int[] buffer = new int[HASH_LENGTH]; for (int i = 0; i < 27; i++) { - buffer = Arrays.copyOfRange(signatureFragment, i * 243, (i + 1) * 243); + buffer = Arrays.copyOfRange(signatureFragment, i * HASH_LENGTH, (i + 1) * HASH_LENGTH); for (int j = normalizedBundleFragment[i] + 13; j-- > 0; ) { jCurl.reset(); @@ -133,8 +146,8 @@ public class Signing { String bundleHash = ""; Transaction trx; List signatureFragments = new ArrayList<>(); - for (int i = 0; i < signedBundle.getTransactions().size(); i++) { + for (int i = 0; i < signedBundle.getTransactions().size(); i++) { trx = signedBundle.getTransactions().get(i); if (trx.getAddress().equals(inputAddress)) { @@ -149,7 +162,6 @@ public class Signing { } } - return validateSignatures(inputAddress, signatureFragments.toArray(new String[signatureFragments.size()]), bundleHash); } @@ -167,13 +179,13 @@ public class Signing { } // Get digests - int[] digests = new int[signatureFragments.length * 243]; + int[] digests = new int[signatureFragments.length * HASH_LENGTH]; for (int i = 0; i < signatureFragments.length; i++) { int[] digestBuffer = digest(normalizedBundleFragments[i % 3], Converter.trits(signatureFragments[i])); - System.arraycopy(digestBuffer, 0, digests, i * 243, 243); + System.arraycopy(digestBuffer, 0, digests, i * HASH_LENGTH, HASH_LENGTH); } String address = Converter.trytes(address(digests)); diff --git a/jota/src/test/java/jota/IotaMultisigTest.java b/jota/src/test/java/jota/IotaMultisigTest.java index 927b5b8..060a679 100644 --- a/jota/src/test/java/jota/IotaMultisigTest.java +++ b/jota/src/test/java/jota/IotaMultisigTest.java @@ -4,6 +4,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import jota.error.InvalidAddressException; import jota.error.InvalidBundleException; +import jota.error.InvalidSecurityLevelException; import jota.error.InvalidTransferException; import jota.model.Bundle; import jota.model.Transaction; @@ -39,7 +40,7 @@ public class IotaMultisigTest { } @Test - public void basicMultiSigTest() throws InvalidBundleException, InvalidTransferException, InvalidAddressException { + public void basicMultiSigTest() throws InvalidBundleException, InvalidTransferException, InvalidAddressException, InvalidSecurityLevelException { Multisig ms = new Multisig(); diff --git a/jota/src/test/java/jota/SigningTest.java b/jota/src/test/java/jota/SigningTest.java index 4f3ecee..34833c8 100644 --- a/jota/src/test/java/jota/SigningTest.java +++ b/jota/src/test/java/jota/SigningTest.java @@ -1,6 +1,7 @@ package jota; import jota.error.InvalidAddressException; +import jota.error.InvalidSecurityLevelException; import jota.model.Bundle; import jota.utils.*; import org.junit.Test; @@ -22,13 +23,13 @@ public class SigningTest { private static final String SIGNATURE2 = "URKFKLNXFEKDOGSQVMAOPEDIWSMTCKJZ9KEVWYALY9JAO9KHUGNDTMGQLKQJUIPWDIVMPEDSVPLFMDCIXDDT9WBBRTFQENL9AXLSBYHINXCDYBFGRNKJDYHAQVJKWCVOYXHTNBEZUNLVMJLUMZYJFAOW9PVVMJZNZZFJQEQFELVFZVFVWPJ9WQZJLPSGBYECHXSFVFQJGUCPFXC9GATTILVCAANNHOYMLOYX9QSUPCERYCOXPACZEEGLREBRZWXGUTTVTHB9GBRCIFEOBPIRXXPQKRSODEHDSZXLGIKXUQWNTQKIOPVDVSIK9WJUAEFOJBU9MBPBSVYSCLBMINTT9ZCTREZSMSVOPXSZOMCGFEZKMOCNLJ9QUTAPKBHRIAIYLCHUQHOINKSCMXWZVDGDXHNJQXJHPCCGBEWROVKEPAPBFFRCAVXZWIRKCRAWYHIHMDXFAGDJQNJJPYSQUHKFOOCEVQOGRQEIOQFKZWUQ9XVRNXKGMJOQEZHQZXQABWUQRBKXWHYUXEAEMDGXVY9WS9VJOCMGBQASSRNKAYJPTSPQEMYSJMTCLMDQJKDPBGQZZSFBDOKHBYY9UDRXNKTPWBCQTVKUGMEDUXL9TTKPATNIKVAGHACHPFSCRYNIRJBQC9OADPGWBFYYARSVNQCGMYQGCYLZH9KLMUIJPCLPQVS9BORXCJBXPDECJGKDNOUYWTKKFLXZARWKGUSMVMXKJTMRYZRERFCFGTZFZFCAOQSZGPQJUEZUJLJPU9QPMJUTZNLMSMPRGIFHUUZHMPMRBEBATEIIWPCOIMWOYOG9NYFBYOWFDKRXOTREBU99GNCPXKOWGI99LNVPRFFF9FCLFXI9HMUFU9NRLNJVTFNUSUJTAVOG9GKUYYEXIM9HTPIDTWIGLKRAQPKMQVZAPYMPSQIOJ9JZBWDMQHDSSRSHNCWSAJCSRORSEXLLQNZUKPXPGRLYMXOXWCCWWSBALFLXPHSGFLTOAFWPETBKJUMBLHMSKYLPJT9EJAZCPPNZWKPVCGKDJCRCLBBIAKVDSNWGONPLKFAYXZDI9FKPHDPKCB9UUPXLJVQTXOAZOQDRNSONXDVSLQGZYRIPGREYHRAUOSBFZDZPZHFNMWCZQGPXCZVLNCSASB9RQDFHOYMUVYLFKOEEWNREYCDMCTZIAFBFKLKRQWZCJHQZCZGWXIFTKRVMPHMVHAABHBDEV9WDEZBR9FLXLNBVNYKUOUFJQKNZVZVGZDDTFYNYFUVRLZKOLXXQYNV9MDVBLZSERXPGYKRIEZQZD9IBKFDT9AIYGWJJCXFWDUDURGJQLXVEJAVEOMZUVVTNCVBXEVQRDQIEHDUCSLCIJUTSCLFXEGMFYP9YLXELCZPMTBZWBIODZCFNJLVWTPQGLMQIHIABAYGJFFMOEDTCXGEDTNXMVXZYFGXRKVVRTIZ9ISXTDHAFPEKQZSM9XXQLOYBLTMD9MBERBIBEJDEXGMOLDZPZVVEPIRKJBDPAKFAWJPTCJSHZPDUKZEEHRFLMZCUGCOWFJBSTDGPHUIXSPPPHRQARMCFMTWKYPJNJQV9VSFZ9EWB9GVEAFUXHWRNUXQLCSBWROOITBATWUXUYGSMGAXKGEBP9ZJWXQWHBVPOSLDHTWXUOFQNO9EXSYPQF9LQLQAFNRU9MTIIRQLBBBYKUPANWRQKGESFARQIRUTGFMZVUKHZJYKTYOARTDOBIYBFRHJWEFHCYVHRHTLTWBRMUDVIVQVNELQMQRXYDNGVSICZINWIZCIWVFXLYOLYKWDNWCWFZUXHUWOPRDHMTSXOZX9CVHANU9ZXTJOGKEPYR9CHGOTIUQSWIALAOIKHQFXWY9ZWTSZADVXJNNZOLSCXVVFBRHLRBTGMSZOYNIXTAMABKGJTLGTZKRHOPPJMNYIQNVKRGXUQDWYEIEZYM9CSXO9YLSBJLDJUWOLUXDEKBGGEIDEXFLZMESDOITNYTNRLGOMHJH9HOLXJABUNLXCZYTXFPZMHRJPLXSVPDBJBBZX9TBIMZZFZOXUSFEJYHEXPFXGJCQTBBLPEEWAPHUETGXSXYYAF9PCCCOONRMQGAPJ9JO9BZQ9QSKTPFFYIFVHSLAZY9CWYSIMKDOSLRKWBHPGJGVEJEEMLCCWXKSOCMBMZZZJWYBBXE9FTAYJALGWITJRXAXWZEXMECTZEEIWZPHYX"; @Test - public void testAddressGeneration() throws InvalidAddressException { + public void testAddressGeneration() throws InvalidAddressException, InvalidSecurityLevelException { 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 { + public void testSigning() throws InvalidAddressException, InvalidSecurityLevelException { // 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 @@ -42,6 +43,17 @@ public class SigningTest { assertEquals(SIGNATURE2, Converter.trytes(signature2)); } + @Test + public void testKeyLength() throws InvalidSecurityLevelException { + Signing signing = new Signing(null); + int[] key = signing.key(Converter.trits(TEST_SEED), 5, 1); + assertEquals(Signing.KEY_LENGTH, key.length); + key = signing.key(Converter.trits(TEST_SEED), 5, 2); + assertEquals(2 * Signing.KEY_LENGTH, key.length); + key = signing.key(Converter.trits(TEST_SEED), 5, 3); + assertEquals(3 * Signing.KEY_LENGTH, key.length); + } + @Test public void testVerifying() throws InvalidAddressException { assertTrue(new Signing(null).validateSignatures(removeChecksum(SIXTH_ADDR), new String[]{SIGNATURE1, SIGNATURE2}, removeChecksum(FIRST_ADDR)));