From 3da083a6d6f94a1b048e9a148c67bb7876faf063 Mon Sep 17 00:00:00 2001 From: semux Date: Mon, 1 Jul 2019 13:36:38 +0100 Subject: [PATCH 1/7] Crypto: add first implementation of the BIP32-ED25519 --- .../semux/crypto/bip32/HdKeyGenerator.java | 76 ++++++++++++++++--- .../org/semux/crypto/bip32/util/HdUtil.java | 46 ++++++++++- .../semux/crypto/bip32/SlipVectorOneTest.java | 70 ++++++++--------- 3 files changed, 140 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java index f14aaa8f4..be253ddfd 100644 --- a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java +++ b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java @@ -11,6 +11,7 @@ import java.math.BigInteger; import java.util.Arrays; +import org.apache.commons.lang3.ArrayUtils; import org.bouncycastle.math.ec.ECPoint; import org.semux.crypto.CryptoException; import org.semux.crypto.bip32.key.HdPrivateKey; @@ -65,7 +66,7 @@ public HdKeyPair getMasterKeyPairFromSeed(byte[] seed, KeyVersion keyVersion, Co privateKey.setFingerprint(new byte[] { 0, 0, 0, 0 }); privateKey.setChildNumber(new byte[] { 0, 0, 0, 0 }); privateKey.setChainCode(IR); - privateKey.setKeyData(HdUtil.append(new byte[] { 0 }, IL)); + privateKey.setKeyData(HdUtil.merge(new byte[] { 0 }, IL)); ECPoint point = Secp256k1.point(masterSecretKey); @@ -82,10 +83,12 @@ public HdKeyPair getMasterKeyPairFromSeed(byte[] seed, KeyVersion keyVersion, Co publicKey.setPublicKey(publicKey.getKeyData()); break; case SLIP10_ED25519: + case BIP32_ED25519: + // FIXME: the `keyData` is incorrect privateKey.setPrivateKey(IL); EdDSAPrivateKey sk = new EdDSAPrivateKey(new EdDSAPrivateKeySpec(IL, ED25519SPEC)); EdDSAPublicKey pk = new EdDSAPublicKey(new EdDSAPublicKeySpec(sk.getA(), sk.getParams())); - publicKey.setPublicKey(HdUtil.append(new byte[] { 0 }, pk.getAbyte())); + publicKey.setPublicKey(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); break; } @@ -116,8 +119,12 @@ public HdPublicKey getChildPublicKey(HdPublicKey parent, long child, boolean isH throw new UnsupportedOperationException("Unable to derive ed25519 public key chaining"); } + if (scheme == Scheme.BIP32_ED25519) { + throw new UnsupportedOperationException("Not yet implemented"); + } + byte[] key = parent.getKeyData(); - byte[] data = HdUtil.append(key, HdUtil.ser32(child)); + byte[] data = HdUtil.merge(key, HdUtil.ser32(child)); // I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)) byte[] I = HmacSha512.hmac512(data, parent.getChainCode()); @@ -183,14 +190,13 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene // ser256(kpar) || ser32(i)). (Note: The 0x00 pads the private key to make it 33 // bytes long.) BigInteger kpar = HdUtil.parse256(parent.getPrivateKey().getKeyData()); - byte[] data = HdUtil.append(new byte[] { 0 }, HdUtil.ser256(kpar)); - data = HdUtil.append(data, HdUtil.ser32(child)); + byte[] data = HdUtil.merge(new byte[] { 0 }, HdUtil.ser256(kpar), HdUtil.ser32(child)); I = HmacSha512.hmac512(data, xChain); } else { // I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)) // just use public key byte[] key = parent.getPublicKey().getKeyData(); - byte[] xPubKey = HdUtil.append(key, HdUtil.ser32(child)); + byte[] xPubKey = HdUtil.merge(key, HdUtil.ser32(child)); I = HmacSha512.hmac512(xPubKey, xChain); } @@ -210,7 +216,7 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene privateKey.setFingerprint(fingerprint); privateKey.setChildNumber(childNumber); privateKey.setChainCode(IR); - privateKey.setKeyData(HdUtil.append(new byte[] { 0 }, HdUtil.ser256(childSecretKey))); + privateKey.setKeyData(HdUtil.merge(new byte[] { 0 }, HdUtil.ser256(childSecretKey))); ECPoint point = Secp256k1.point(childSecretKey); @@ -233,16 +239,52 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene publicKey.setPublicKey(publicKey.getKeyData()); break; case SLIP10_ED25519: + case BIP32_ED25519: + if (parent.getCoinType().getScheme() == Scheme.BIP32_ED25519) { + byte[] kLP = Arrays.copyOfRange(parent.getPrivateKey().getKeyData(), 1, 33); + byte[] kRP = parent.getPrivateKey().getChainCode(); + byte[] AP = parent.getPublicKey().getPublicKey(); + + byte[] Z; + if (isHardened) { + byte[] data = HdUtil.merge(new byte[] { 0 }, kLP, kRP, HdUtil.ser32LE(child)); + Z = HmacSha512.hmac512(data, parent.getPrivateKey().getChainCode()); + } else { + byte[] data = HdUtil.merge(new byte[] { 2 }, AP, HdUtil.ser32LE(child)); + Z = HmacSha512.hmac512(data, parent.getPrivateKey().getChainCode()); + } + byte[] ZL = Arrays.copyOfRange(Z, 0, 28); + byte[] ZR = Arrays.copyOfRange(Z, 32, 64); + BigInteger kLiBI = parseBigIntegerLE(ZL) + .multiply(BigInteger.valueOf(8)) + .add(parseBigIntegerLE(kLP)); + BigInteger order = BigInteger.valueOf(2).pow(252) + .add(new BigInteger("27742317777372353535851937790883648493")); + if (kLiBI.mod(order).equals(BigInteger.ZERO)) { + return null; + } + byte[] kLi = serializeBigIntegerLE(kLiBI); + BigInteger kRiBI = parseBigIntegerLE(ZR) + .add(parseBigIntegerLE(kRP)) + .mod(BigInteger.valueOf(2).pow(256)); + byte[] kRi = serializeBigIntegerLE(kRiBI); + + IL = kLi; + IR = kRi; + privateKey.setChainCode(IR); + publicKey.setChainCode(IR); + } + privateKey.setPrivateKey(IL); h160 = HashUtil.h160(parent.getPublicKey().getPublicKey()); childFingerprint = new byte[] { h160[0], h160[1], h160[2], h160[3] }; publicKey.setFingerprint(childFingerprint); privateKey.setFingerprint(childFingerprint); - privateKey.setKeyData(HdUtil.append(new byte[] { 0 }, IL)); + privateKey.setKeyData(HdUtil.merge(new byte[] { 0 }, IL)); EdDSAPrivateKey sk = new EdDSAPrivateKey(new EdDSAPrivateKeySpec(IL, ED25519SPEC)); EdDSAPublicKey pk = new EdDSAPublicKey(new EdDSAPublicKeySpec(sk.getA(), sk.getParams())); - publicKey.setPublicKey(HdUtil.append(new byte[] { 0 }, pk.getAbyte())); + publicKey.setPublicKey(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); break; } @@ -255,4 +297,20 @@ private String getPath(String parentPath, long child, boolean isHardened) { } return parentPath + "/" + child + (isHardened ? "'" : ""); } + + private BigInteger parseBigIntegerLE(byte[] bytes) { + byte[] temp = bytes.clone(); + ArrayUtils.reverse(temp); + return new BigInteger(1, temp); + } + + private byte[] serializeBigIntegerLE(BigInteger bi) { + byte[] temp = bi.toByteArray(); + ArrayUtils.reverse(temp); + if (temp.length < 32) { + return Arrays.copyOf(temp, 32); + } else { + return temp; + } + } } diff --git a/src/main/java/org/semux/crypto/bip32/util/HdUtil.java b/src/main/java/org/semux/crypto/bip32/util/HdUtil.java index 2427bccaa..ccfc68515 100644 --- a/src/main/java/org/semux/crypto/bip32/util/HdUtil.java +++ b/src/main/java/org/semux/crypto/bip32/util/HdUtil.java @@ -8,6 +8,9 @@ import java.math.BigInteger; import java.util.Arrays; +import java.util.stream.Stream; + +import org.apache.commons.lang3.ArrayUtils; /** * General Util class for defined functions. @@ -23,7 +26,6 @@ public class HdUtil { * @return ser32(i) */ public static byte[] ser32(long i) { - byte[] ser = new byte[4]; ser[0] = (byte) (i >> 24); ser[1] = (byte) (i >> 16); @@ -33,8 +35,25 @@ public static byte[] ser32(long i) { } /** - * ser256(p): serializes the integer p as a 32-byte sequence, most significant - * byte first. + * ser32(i): serialize a 32-bit unsigned integer i as a 4-byte sequence, least + * significant byte first. + *

+ * Prefer long type to hold unsigned ints. + * + * @return ser32LE(i) + */ + public static byte[] ser32LE(long i) { + byte[] ser = new byte[4]; + ser[3] = (byte) (i >> 24); + ser[2] = (byte) (i >> 16); + ser[1] = (byte) (i >> 8); + ser[0] = (byte) (i); + return ser; + } + + /** + * ser256(p): serializes the integer p as a 32-byte sequence, most + * significant byte first. * * @param p * big integer @@ -78,13 +97,32 @@ public static BigInteger parse256(byte[] p) { * second byte array * @return bytes appended */ - public static byte[] append(byte[] a, byte[] b) { + public static byte[] merge(byte[] a, byte[] b) { byte[] c = new byte[a.length + b.length]; System.arraycopy(a, 0, c, 0, a.length); System.arraycopy(b, 0, c, a.length, b.length); return c; } + /** + * Merge byte arrays. + * + * @param arrays + * @return + */ + public static byte[] merge(byte[] ...arrays) { + int total = Stream.of(arrays).mapToInt(a -> a.length).sum(); + + byte[] buffer = new byte[total]; + int start = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, buffer, start, array.length); + start += array.length; + } + + return buffer; + } + /** * Get the fingerprint * diff --git a/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java b/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java index b241e566c..b087786bc 100644 --- a/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java +++ b/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java @@ -7,8 +7,6 @@ package org.semux.crypto.bip32; -import static org.junit.Assert.fail; - import org.junit.Assert; import org.junit.Test; import org.semux.crypto.Hex; @@ -26,25 +24,19 @@ public void testVectorOneMaster() { byte[] privateKey = Hex.decode("2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7"); byte[] publicKey = Hex.decode("00a4b2856bfec510abab89753fac1ac0e1112364e7d250545963f135f2a33188ed"); - HdKeyPair address = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); + HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); - Assert.assertArrayEquals(fingerprint, address.getPrivateKey().getFingerprint()); - Assert.assertArrayEquals(chainCode, address.getPrivateKey().getChainCode()); - Assert.assertArrayEquals(privateKey, address.getPrivateKey().getPrivateKey()); - Assert.assertArrayEquals(publicKey, address.getPublicKey().getPublicKey()); - Assert.assertEquals(Scheme.SLIP10_ED25519, address.getCoinType().getScheme()); + Assert.assertArrayEquals(fingerprint, master.getPrivateKey().getFingerprint()); + Assert.assertArrayEquals(chainCode, master.getPrivateKey().getChainCode()); + Assert.assertArrayEquals(privateKey, master.getPrivateKey().getPrivateKey()); + Assert.assertArrayEquals(publicKey, master.getPublicKey().getPublicKey()); + Assert.assertEquals(Scheme.SLIP10_ED25519, master.getCoinType().getScheme()); } - @Test + @Test(expected = UnsupportedOperationException.class) public void testUnableToPublicChain() { HdKeyPair address = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); - try { - hdKeyGenerator.getChildPublicKey(address.getPublicKey(), 0, false, Scheme.SLIP10_ED25519); - fail("Should not be able to chain public keys with ed25519"); - - } catch (UnsupportedOperationException e) { - // expected - } + hdKeyGenerator.getChildPublicKey(address.getPublicKey(), 0, false, Scheme.SLIP10_ED25519); } @Test @@ -55,13 +47,13 @@ public void testVector0H() { byte[] publicKey = Hex.decode("008c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c"); HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); - HdKeyPair address = hdKeyGenerator.getChildKeyPair(master, 0, true); + HdKeyPair child = hdKeyGenerator.getChildKeyPair(master, 0, true); - Assert.assertArrayEquals(fingerprint, address.getPrivateKey().getFingerprint()); - Assert.assertArrayEquals(chainCode, address.getPrivateKey().getChainCode()); - Assert.assertArrayEquals(privateKey, address.getPrivateKey().getPrivateKey()); - Assert.assertArrayEquals(publicKey, address.getPublicKey().getPublicKey()); - Assert.assertEquals(Scheme.SLIP10_ED25519, address.getCoinType().getScheme()); + Assert.assertArrayEquals(fingerprint, child.getPrivateKey().getFingerprint()); + Assert.assertArrayEquals(chainCode, child.getPrivateKey().getChainCode()); + Assert.assertArrayEquals(privateKey, child.getPrivateKey().getPrivateKey()); + Assert.assertArrayEquals(publicKey, child.getPublicKey().getPublicKey()); + Assert.assertEquals(Scheme.SLIP10_ED25519, child.getCoinType().getScheme()); } @Test @@ -72,14 +64,14 @@ public void testVector0H1H() { byte[] publicKey = Hex.decode("001932a5270f335bed617d5b935c80aedb1a35bd9fc1e31acafd5372c30f5c1187"); HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); - HdKeyPair address = hdKeyGenerator.getChildKeyPair(master, 0, true); - address = hdKeyGenerator.getChildKeyPair(address, 1, true); - - Assert.assertArrayEquals(fingerprint, address.getPrivateKey().getFingerprint()); - Assert.assertArrayEquals(chainCode, address.getPrivateKey().getChainCode()); - Assert.assertArrayEquals(privateKey, address.getPrivateKey().getPrivateKey()); - Assert.assertArrayEquals(publicKey, address.getPublicKey().getPublicKey()); - Assert.assertEquals(Scheme.SLIP10_ED25519, address.getCoinType().getScheme()); + HdKeyPair child = hdKeyGenerator.getChildKeyPair(master, 0, true); + child = hdKeyGenerator.getChildKeyPair(child, 1, true); + + Assert.assertArrayEquals(fingerprint, child.getPrivateKey().getFingerprint()); + Assert.assertArrayEquals(chainCode, child.getPrivateKey().getChainCode()); + Assert.assertArrayEquals(privateKey, child.getPrivateKey().getPrivateKey()); + Assert.assertArrayEquals(publicKey, child.getPublicKey().getPublicKey()); + Assert.assertEquals(Scheme.SLIP10_ED25519, child.getCoinType().getScheme()); } @Test @@ -90,14 +82,14 @@ public void testVector0H1H2H() { byte[] publicKey = Hex.decode("00ae98736566d30ed0e9d2f4486a64bc95740d89c7db33f52121f8ea8f76ff0fc1"); HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); - HdKeyPair address = hdKeyGenerator.getChildKeyPair(master, 0, true); - address = hdKeyGenerator.getChildKeyPair(address, 1, true); - address = hdKeyGenerator.getChildKeyPair(address, 2, true); - - Assert.assertArrayEquals(fingerprint, address.getPrivateKey().getFingerprint()); - Assert.assertArrayEquals(chainCode, address.getPrivateKey().getChainCode()); - Assert.assertArrayEquals(privateKey, address.getPrivateKey().getPrivateKey()); - Assert.assertArrayEquals(publicKey, address.getPublicKey().getPublicKey()); - Assert.assertEquals(Scheme.SLIP10_ED25519, address.getCoinType().getScheme()); + HdKeyPair child = hdKeyGenerator.getChildKeyPair(master, 0, true); + child = hdKeyGenerator.getChildKeyPair(child, 1, true); + child = hdKeyGenerator.getChildKeyPair(child, 2, true); + + Assert.assertArrayEquals(fingerprint, child.getPrivateKey().getFingerprint()); + Assert.assertArrayEquals(chainCode, child.getPrivateKey().getChainCode()); + Assert.assertArrayEquals(privateKey, child.getPrivateKey().getPrivateKey()); + Assert.assertArrayEquals(publicKey, child.getPublicKey().getPublicKey()); + Assert.assertEquals(Scheme.SLIP10_ED25519, child.getCoinType().getScheme()); } } From 90e1f9619cbe9bb097a6278481ca3df9b0f31b1c Mon Sep 17 00:00:00 2001 From: semux Date: Mon, 1 Jul 2019 14:53:48 +0100 Subject: [PATCH 2/7] Crypto: re-use the keydata for both public and private keys --- src/main/java/org/semux/core/Wallet.java | 4 +- .../semux/crypto/bip32/HdKeyGenerator.java | 108 ++++++++---------- .../semux/crypto/bip32/key/HdPrivateKey.java | 9 -- .../semux/crypto/bip32/key/HdPublicKey.java | 9 -- .../org/semux/crypto/bip32/util/HdUtil.java | 6 +- .../semux/crypto/bip32/SlipVectorOneTest.java | 16 +-- 6 files changed, 59 insertions(+), 93 deletions(-) diff --git a/src/main/java/org/semux/core/Wallet.java b/src/main/java/org/semux/core/Wallet.java index c665b89ee..1cf41936d 100644 --- a/src/main/java/org/semux/core/Wallet.java +++ b/src/main/java/org/semux/core/Wallet.java @@ -691,7 +691,7 @@ public Key addAccountWithNextHdKey() { synchronized (accounts) { HdKeyPair rootAddress = BIP_44.getRootAddressFromSeed(hdSeed, KeyVersion.MAINNET, CoinType.SEMUX); HdKeyPair address = BIP_44.getAddress(rootAddress, nextHdAccountIndex++); - Key newKey = Key.fromRawPrivateKey(address.getPrivateKey().getPrivateKey()); + Key newKey = Key.fromRawPrivateKey(address.getPrivateKey().getKeyData()); ByteArray to = ByteArray.of(newKey.toAddress()); // put the accounts into @@ -733,7 +733,7 @@ public int scanForHdKeys(AccountState accountState) { for (int i = start; i < endIndex; i++) { HdKeyPair address = BIP_44.getAddress(rootAddress, i); - Key key = Key.fromRawPrivateKey(address.getPrivateKey().getPrivateKey()); + Key key = Key.fromRawPrivateKey(address.getPrivateKey().getKeyData()); boolean isUsedAccount = isUsedAccount(accountState, key.toAddress()); ByteArray to = ByteArray.of(key.toAddress()); diff --git a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java index be253ddfd..e7d176d27 100644 --- a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java +++ b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java @@ -40,7 +40,7 @@ public HdKeyPair getMasterKeyPairFromSeed(byte[] seed, KeyVersion keyVersion, Co Scheme scheme = coinType.getScheme(); HdPublicKey publicKey = new HdPublicKey(); HdPrivateKey privateKey = new HdPrivateKey(); - HdKeyPair address = new HdKeyPair(privateKey, publicKey, coinType, MASTER_PATH); + HdKeyPair key = new HdKeyPair(privateKey, publicKey, coinType, MASTER_PATH); byte[] I; try { @@ -68,31 +68,21 @@ public HdKeyPair getMasterKeyPairFromSeed(byte[] seed, KeyVersion keyVersion, Co privateKey.setChainCode(IR); privateKey.setKeyData(HdUtil.merge(new byte[] { 0 }, IL)); - ECPoint point = Secp256k1.point(masterSecretKey); - publicKey.setVersion(keyVersion.getPublicKeyVersion()); publicKey.setDepth(0); publicKey.setFingerprint(new byte[] { 0, 0, 0, 0 }); publicKey.setChildNumber(new byte[] { 0, 0, 0, 0 }); publicKey.setChainCode(IR); - publicKey.setKeyData(Secp256k1.serP(point)); + publicKey.setKeyData(Secp256k1.serP(Secp256k1.point(masterSecretKey))); - switch (scheme) { - case BIP32: - privateKey.setPrivateKey(privateKey.getKeyData()); - publicKey.setPublicKey(publicKey.getKeyData()); - break; - case SLIP10_ED25519: - case BIP32_ED25519: - // FIXME: the `keyData` is incorrect - privateKey.setPrivateKey(IL); + if (scheme == Scheme.SLIP10_ED25519 || scheme == Scheme.BIP32_ED25519) { + privateKey.setKeyData(IL); EdDSAPrivateKey sk = new EdDSAPrivateKey(new EdDSAPrivateKeySpec(IL, ED25519SPEC)); EdDSAPublicKey pk = new EdDSAPublicKey(new EdDSAPublicKeySpec(sk.getA(), sk.getParams())); - publicKey.setPublicKey(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); - break; + publicKey.setKeyData(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); } - return address; + return key; } /** @@ -233,59 +223,53 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene publicKey.setChainCode(IR); publicKey.setKeyData(Secp256k1.serP(point)); - switch (parent.getCoinType().getScheme()) { - case BIP32: - privateKey.setPrivateKey(privateKey.getKeyData()); - publicKey.setPublicKey(publicKey.getKeyData()); - break; - case SLIP10_ED25519: - case BIP32_ED25519: - if (parent.getCoinType().getScheme() == Scheme.BIP32_ED25519) { - byte[] kLP = Arrays.copyOfRange(parent.getPrivateKey().getKeyData(), 1, 33); - byte[] kRP = parent.getPrivateKey().getChainCode(); - byte[] AP = parent.getPublicKey().getPublicKey(); - - byte[] Z; - if (isHardened) { - byte[] data = HdUtil.merge(new byte[] { 0 }, kLP, kRP, HdUtil.ser32LE(child)); - Z = HmacSha512.hmac512(data, parent.getPrivateKey().getChainCode()); - } else { - byte[] data = HdUtil.merge(new byte[] { 2 }, AP, HdUtil.ser32LE(child)); - Z = HmacSha512.hmac512(data, parent.getPrivateKey().getChainCode()); - } - byte[] ZL = Arrays.copyOfRange(Z, 0, 28); - byte[] ZR = Arrays.copyOfRange(Z, 32, 64); - BigInteger kLiBI = parseBigIntegerLE(ZL) - .multiply(BigInteger.valueOf(8)) - .add(parseBigIntegerLE(kLP)); - BigInteger order = BigInteger.valueOf(2).pow(252) - .add(new BigInteger("27742317777372353535851937790883648493")); - if (kLiBI.mod(order).equals(BigInteger.ZERO)) { - return null; - } - byte[] kLi = serializeBigIntegerLE(kLiBI); - BigInteger kRiBI = parseBigIntegerLE(ZR) - .add(parseBigIntegerLE(kRP)) - .mod(BigInteger.valueOf(2).pow(256)); - byte[] kRi = serializeBigIntegerLE(kRiBI); - - IL = kLi; - IR = kRi; - privateKey.setChainCode(IR); - publicKey.setChainCode(IR); + Scheme scheme = parent.getCoinType().getScheme(); + if (scheme == Scheme.BIP32_ED25519) { + byte[] kLP = parent.getPrivateKey().getKeyData(); + byte[] kRP = parent.getPrivateKey().getChainCode(); + byte[] AP = Arrays.copyOfRange(parent.getPublicKey().getKeyData(), 1, 33); + + byte[] Z; + if (isHardened) { + byte[] data = HdUtil.merge(new byte[] { 0 }, kLP, kRP, HdUtil.ser32LE(child)); + Z = HmacSha512.hmac512(data, parent.getPrivateKey().getChainCode()); + } else { + byte[] data = HdUtil.merge(new byte[] { 2 }, AP, HdUtil.ser32LE(child)); + Z = HmacSha512.hmac512(data, parent.getPrivateKey().getChainCode()); + } + byte[] ZL = Arrays.copyOfRange(Z, 0, 28); + byte[] ZR = Arrays.copyOfRange(Z, 32, 64); + BigInteger kLiBI = parseBigIntegerLE(ZL) + .multiply(BigInteger.valueOf(8)) + .add(parseBigIntegerLE(kLP)); + BigInteger order = BigInteger.valueOf(2).pow(252) + .add(new BigInteger("27742317777372353535851937790883648493")); + if (kLiBI.mod(order).equals(BigInteger.ZERO)) { + return null; } + byte[] kLi = serializeBigIntegerLE(kLiBI); + BigInteger kRiBI = parseBigIntegerLE(ZR) + .add(parseBigIntegerLE(kRP)) + .mod(BigInteger.valueOf(2).pow(256)); + byte[] kRi = serializeBigIntegerLE(kRiBI); + + IL = kLi; + IR = kRi; + } - privateKey.setPrivateKey(IL); - h160 = HashUtil.h160(parent.getPublicKey().getPublicKey()); + if (scheme == Scheme.SLIP10_ED25519 || scheme == Scheme.BIP32_ED25519) { + h160 = HashUtil.h160(parent.getPublicKey().getKeyData()); childFingerprint = new byte[] { h160[0], h160[1], h160[2], h160[3] }; - publicKey.setFingerprint(childFingerprint); + privateKey.setFingerprint(childFingerprint); - privateKey.setKeyData(HdUtil.merge(new byte[] { 0 }, IL)); + privateKey.setChainCode(IR); + privateKey.setKeyData(IL); EdDSAPrivateKey sk = new EdDSAPrivateKey(new EdDSAPrivateKeySpec(IL, ED25519SPEC)); EdDSAPublicKey pk = new EdDSAPublicKey(new EdDSAPublicKeySpec(sk.getA(), sk.getParams())); - publicKey.setPublicKey(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); - break; + publicKey.setFingerprint(childFingerprint); + privateKey.setChainCode(IR); + publicKey.setKeyData(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); } return address; diff --git a/src/main/java/org/semux/crypto/bip32/key/HdPrivateKey.java b/src/main/java/org/semux/crypto/bip32/key/HdPrivateKey.java index b6a616134..f795fe11e 100644 --- a/src/main/java/org/semux/crypto/bip32/key/HdPrivateKey.java +++ b/src/main/java/org/semux/crypto/bip32/key/HdPrivateKey.java @@ -11,13 +11,4 @@ * Defines a key with a given private key */ public class HdPrivateKey extends HdKey { - private byte[] privateKey; - - public byte[] getPrivateKey() { - return privateKey; - } - - public void setPrivateKey(byte[] privateKey) { - this.privateKey = privateKey; - } } diff --git a/src/main/java/org/semux/crypto/bip32/key/HdPublicKey.java b/src/main/java/org/semux/crypto/bip32/key/HdPublicKey.java index 430f9ce4c..261bf10d5 100644 --- a/src/main/java/org/semux/crypto/bip32/key/HdPublicKey.java +++ b/src/main/java/org/semux/crypto/bip32/key/HdPublicKey.java @@ -11,13 +11,4 @@ * Defines a key with a given public key */ public class HdPublicKey extends HdKey { - private byte[] publicKey; - - public byte[] getPublicKey() { - return publicKey; - } - - public void setPublicKey(byte[] publicKey) { - this.publicKey = publicKey; - } } diff --git a/src/main/java/org/semux/crypto/bip32/util/HdUtil.java b/src/main/java/org/semux/crypto/bip32/util/HdUtil.java index ccfc68515..ea59e6609 100644 --- a/src/main/java/org/semux/crypto/bip32/util/HdUtil.java +++ b/src/main/java/org/semux/crypto/bip32/util/HdUtil.java @@ -52,8 +52,8 @@ public static byte[] ser32LE(long i) { } /** - * ser256(p): serializes the integer p as a 32-byte sequence, most - * significant byte first. + * ser256(p): serializes the integer p as a 32-byte sequence, most significant + * byte first. * * @param p * big integer @@ -110,7 +110,7 @@ public static byte[] merge(byte[] a, byte[] b) { * @param arrays * @return */ - public static byte[] merge(byte[] ...arrays) { + public static byte[] merge(byte[]... arrays) { int total = Stream.of(arrays).mapToInt(a -> a.length).sum(); byte[] buffer = new byte[total]; diff --git a/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java b/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java index b087786bc..d94e8707b 100644 --- a/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java +++ b/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java @@ -28,8 +28,8 @@ public void testVectorOneMaster() { Assert.assertArrayEquals(fingerprint, master.getPrivateKey().getFingerprint()); Assert.assertArrayEquals(chainCode, master.getPrivateKey().getChainCode()); - Assert.assertArrayEquals(privateKey, master.getPrivateKey().getPrivateKey()); - Assert.assertArrayEquals(publicKey, master.getPublicKey().getPublicKey()); + Assert.assertArrayEquals(privateKey, master.getPrivateKey().getKeyData()); + Assert.assertArrayEquals(publicKey, master.getPublicKey().getKeyData()); Assert.assertEquals(Scheme.SLIP10_ED25519, master.getCoinType().getScheme()); } @@ -51,8 +51,8 @@ public void testVector0H() { Assert.assertArrayEquals(fingerprint, child.getPrivateKey().getFingerprint()); Assert.assertArrayEquals(chainCode, child.getPrivateKey().getChainCode()); - Assert.assertArrayEquals(privateKey, child.getPrivateKey().getPrivateKey()); - Assert.assertArrayEquals(publicKey, child.getPublicKey().getPublicKey()); + Assert.assertArrayEquals(privateKey, child.getPrivateKey().getKeyData()); + Assert.assertArrayEquals(publicKey, child.getPublicKey().getKeyData()); Assert.assertEquals(Scheme.SLIP10_ED25519, child.getCoinType().getScheme()); } @@ -69,8 +69,8 @@ public void testVector0H1H() { Assert.assertArrayEquals(fingerprint, child.getPrivateKey().getFingerprint()); Assert.assertArrayEquals(chainCode, child.getPrivateKey().getChainCode()); - Assert.assertArrayEquals(privateKey, child.getPrivateKey().getPrivateKey()); - Assert.assertArrayEquals(publicKey, child.getPublicKey().getPublicKey()); + Assert.assertArrayEquals(privateKey, child.getPrivateKey().getKeyData()); + Assert.assertArrayEquals(publicKey, child.getPublicKey().getKeyData()); Assert.assertEquals(Scheme.SLIP10_ED25519, child.getCoinType().getScheme()); } @@ -88,8 +88,8 @@ public void testVector0H1H2H() { Assert.assertArrayEquals(fingerprint, child.getPrivateKey().getFingerprint()); Assert.assertArrayEquals(chainCode, child.getPrivateKey().getChainCode()); - Assert.assertArrayEquals(privateKey, child.getPrivateKey().getPrivateKey()); - Assert.assertArrayEquals(publicKey, child.getPublicKey().getPublicKey()); + Assert.assertArrayEquals(privateKey, child.getPrivateKey().getKeyData()); + Assert.assertArrayEquals(publicKey, child.getPublicKey().getKeyData()); Assert.assertEquals(Scheme.SLIP10_ED25519, child.getCoinType().getScheme()); } } From ee4f8a71576a924ed1ce924bae5941354fee945a Mon Sep 17 00:00:00 2001 From: semux Date: Mon, 1 Jul 2019 15:01:13 +0100 Subject: [PATCH 3/7] Trivial: rename variable --- src/main/java/org/semux/core/Wallet.java | 16 ++++++++-------- .../org/semux/crypto/bip32/HdKeyGenerator.java | 9 ++++----- .../java/org/semux/crypto/bip44/Bip44.java | 18 +++++++++--------- .../semux/crypto/bip32/PublicKeyChainTest.java | 8 ++++---- .../semux/crypto/bip32/SlipVectorOneTest.java | 4 ++-- .../org/semux/crypto/bip39/VectorTest.java | 4 ++-- .../java/org/semux/crypto/bip44/Bip44Test.java | 8 ++++---- 7 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/semux/core/Wallet.java b/src/main/java/org/semux/core/Wallet.java index 1cf41936d..ffe68e5c6 100644 --- a/src/main/java/org/semux/core/Wallet.java +++ b/src/main/java/org/semux/core/Wallet.java @@ -689,9 +689,9 @@ public Key addAccountWithNextHdKey() { requireHdWalletInitialized(); synchronized (accounts) { - HdKeyPair rootAddress = BIP_44.getRootAddressFromSeed(hdSeed, KeyVersion.MAINNET, CoinType.SEMUX); - HdKeyPair address = BIP_44.getAddress(rootAddress, nextHdAccountIndex++); - Key newKey = Key.fromRawPrivateKey(address.getPrivateKey().getKeyData()); + HdKeyPair rootKey = BIP_44.getRootKeyPairFromSeed(hdSeed, KeyVersion.MAINNET, CoinType.SEMUX); + HdKeyPair key = BIP_44.getChildKeyPair(rootKey, nextHdAccountIndex++); + Key newKey = Key.fromRawPrivateKey(key.getPrivateKey().getKeyData()); ByteArray to = ByteArray.of(newKey.toAddress()); // put the accounts into @@ -699,7 +699,7 @@ public Key addAccountWithNextHdKey() { // set a default alias if (!aliases.containsKey(to)) { - setAddressAlias(newKey.toAddress(), getAliasFromPath(address.getPath())); + setAddressAlias(newKey.toAddress(), getAliasFromPath(key.getPath())); } return newKey; @@ -723,7 +723,7 @@ public int scanForHdKeys(AccountState accountState) { found++; } - HdKeyPair rootAddress = BIP_44.getRootAddressFromSeed(hdSeed, getKeyVersion(network), CoinType.SEMUX); + HdKeyPair rootAddress = BIP_44.getRootKeyPairFromSeed(hdSeed, getKeyVersion(network), CoinType.SEMUX); nextHdAccountIndex = 0; @@ -731,9 +731,9 @@ public int scanForHdKeys(AccountState accountState) { int endIndex = start + MAX_HD_WALLET_SCAN_AHEAD; for (int i = start; i < endIndex; i++) { - HdKeyPair address = BIP_44.getAddress(rootAddress, i); + HdKeyPair childKey = BIP_44.getChildKeyPair(rootAddress, i); - Key key = Key.fromRawPrivateKey(address.getPrivateKey().getKeyData()); + Key key = Key.fromRawPrivateKey(childKey.getPrivateKey().getKeyData()); boolean isUsedAccount = isUsedAccount(accountState, key.toAddress()); ByteArray to = ByteArray.of(key.toAddress()); @@ -743,7 +743,7 @@ public int scanForHdKeys(AccountState accountState) { endIndex += MAX_HD_WALLET_SCAN_AHEAD; if (addAccount(key)) { if (!aliases.containsKey(to)) { - aliases.put(to, address.getPath()); + aliases.put(to, childKey.getPath()); } found++; } diff --git a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java index e7d176d27..50738ca9b 100644 --- a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java +++ b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java @@ -163,7 +163,7 @@ public HdPublicKey getChildPublicKey(HdPublicKey parent, long child, boolean isH public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardened) { HdPrivateKey privateKey = new HdPrivateKey(); HdPublicKey publicKey = new HdPublicKey(); - HdKeyPair address = new HdKeyPair(privateKey, publicKey, parent.getCoinType(), + HdKeyPair key = new HdKeyPair(privateKey, publicKey, parent.getCoinType(), getPath(parent.getPath(), child, isHardened)); if (isHardened) { @@ -185,9 +185,8 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene } else { // I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)) // just use public key - byte[] key = parent.getPublicKey().getKeyData(); - byte[] xPubKey = HdUtil.merge(key, HdUtil.ser32(child)); - I = HmacSha512.hmac512(xPubKey, xChain); + byte[] data = HdUtil.merge(parent.getPublicKey().getKeyData(), HdUtil.ser32(child)); + I = HmacSha512.hmac512(data, xChain); } // split into left/right @@ -272,7 +271,7 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene publicKey.setKeyData(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); } - return address; + return key; } private String getPath(String parentPath, long child, boolean isHardened) { diff --git a/src/main/java/org/semux/crypto/bip44/Bip44.java b/src/main/java/org/semux/crypto/bip44/Bip44.java index e98ff0553..87b91d08f 100644 --- a/src/main/java/org/semux/crypto/bip44/Bip44.java +++ b/src/main/java/org/semux/crypto/bip44/Bip44.java @@ -35,17 +35,17 @@ public class Bip44 { * coinType * @return */ - public HdKeyPair getRootAddressFromSeed(byte[] seed, KeyVersion keyVersion, CoinType coinType) { - HdKeyPair masterAddress = hdKeyGenerator.getMasterKeyPairFromSeed(seed, keyVersion, coinType); - HdKeyPair purposeAddress = hdKeyGenerator.getChildKeyPair(masterAddress, PURPOSE, true); - HdKeyPair coinTypeAddress = hdKeyGenerator.getChildKeyPair(purposeAddress, coinType.getCoinType(), true); - HdKeyPair accountAddress = hdKeyGenerator.getChildKeyPair(coinTypeAddress, ACCOUNT, true); - HdKeyPair changeAddress = hdKeyGenerator.getChildKeyPair(accountAddress, CHANGE, coinType.getAlwaysHardened()); + public HdKeyPair getRootKeyPairFromSeed(byte[] seed, KeyVersion keyVersion, CoinType coinType) { + HdKeyPair masterKey = hdKeyGenerator.getMasterKeyPairFromSeed(seed, keyVersion, coinType); + HdKeyPair purposeKey = hdKeyGenerator.getChildKeyPair(masterKey, PURPOSE, true); + HdKeyPair coinTypeKey = hdKeyGenerator.getChildKeyPair(purposeKey, coinType.getCoinType(), true); + HdKeyPair accountKey = hdKeyGenerator.getChildKeyPair(coinTypeKey, ACCOUNT, true); + HdKeyPair changeKey = hdKeyGenerator.getChildKeyPair(accountKey, CHANGE, coinType.getAlwaysHardened()); - return changeAddress; + return changeKey; } - public HdKeyPair getAddress(HdKeyPair address, int index) { - return hdKeyGenerator.getChildKeyPair(address, index, address.getCoinType().getAlwaysHardened()); + public HdKeyPair getChildKeyPair(HdKeyPair parent, int index) { + return hdKeyGenerator.getChildKeyPair(parent, index, parent.getCoinType().getAlwaysHardened()); } } diff --git a/src/test/java/org/semux/crypto/bip32/PublicKeyChainTest.java b/src/test/java/org/semux/crypto/bip32/PublicKeyChainTest.java index 33e34251c..67dba54aa 100644 --- a/src/test/java/org/semux/crypto/bip32/PublicKeyChainTest.java +++ b/src/test/java/org/semux/crypto/bip32/PublicKeyChainTest.java @@ -21,10 +21,10 @@ public class PublicKeyChainTest { @Test public void testPubKey0() { - HdKeyPair rootAddress = generator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.BITCOIN); - HdKeyPair address = generator.getChildKeyPair(rootAddress, 0, false); + HdKeyPair rootKeyPair = generator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.BITCOIN); + HdKeyPair childKeyPair = generator.getChildKeyPair(rootKeyPair, 0, false); // test that the pub key chain generated from only public key matches the other - HdPublicKey pubKey = generator.getChildPublicKey(rootAddress.getPublicKey(), 0, false, Scheme.BIP32); - Assert.assertArrayEquals(address.getPublicKey().getKey(), pubKey.getKey()); + HdPublicKey pubKey = generator.getChildPublicKey(rootKeyPair.getPublicKey(), 0, false, Scheme.BIP32); + Assert.assertArrayEquals(childKeyPair.getPublicKey().getKey(), pubKey.getKey()); } } diff --git a/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java b/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java index d94e8707b..eab9e234e 100644 --- a/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java +++ b/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java @@ -35,8 +35,8 @@ public void testVectorOneMaster() { @Test(expected = UnsupportedOperationException.class) public void testUnableToPublicChain() { - HdKeyPair address = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); - hdKeyGenerator.getChildPublicKey(address.getPublicKey(), 0, false, Scheme.SLIP10_ED25519); + HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); + hdKeyGenerator.getChildPublicKey(master.getPublicKey(), 0, false, Scheme.SLIP10_ED25519); } @Test diff --git a/src/test/java/org/semux/crypto/bip39/VectorTest.java b/src/test/java/org/semux/crypto/bip39/VectorTest.java index bb9df26bc..2cc094094 100644 --- a/src/test/java/org/semux/crypto/bip39/VectorTest.java +++ b/src/test/java/org/semux/crypto/bip39/VectorTest.java @@ -44,8 +44,8 @@ public void testVectors() throws IOException { Assert.assertEquals(vector.getMnemonic(), words); byte[] seed = generator.getSeedFromWordlist(words, password, lang); Assert.assertEquals(vector.getSeed(), Hex.encode(seed)); - HdKeyPair address = keyGenerator.getMasterKeyPairFromSeed(seed, KeyVersion.MAINNET, CoinType.BITCOIN); - Assert.assertEquals(vector.getHdKey(), Base58.encode(address.getPrivateKey().getKey())); + HdKeyPair key = keyGenerator.getMasterKeyPairFromSeed(seed, KeyVersion.MAINNET, CoinType.BITCOIN); + Assert.assertEquals(vector.getHdKey(), Base58.encode(key.getPrivateKey().getKey())); } } } diff --git a/src/test/java/org/semux/crypto/bip44/Bip44Test.java b/src/test/java/org/semux/crypto/bip44/Bip44Test.java index 9977b0fba..034a1be59 100644 --- a/src/test/java/org/semux/crypto/bip44/Bip44Test.java +++ b/src/test/java/org/semux/crypto/bip44/Bip44Test.java @@ -19,13 +19,13 @@ public class Bip44Test { @Test public void testBitcoin() { - HdKeyPair address = bip44.getRootAddressFromSeed(seed, KeyVersion.MAINNET, CoinType.BITCOIN); - bip44.getAddress(address, 0); + HdKeyPair key = bip44.getRootKeyPairFromSeed(seed, KeyVersion.MAINNET, CoinType.BITCOIN); + bip44.getChildKeyPair(key, 0); } @Test public void testSemux() { - HdKeyPair address = bip44.getRootAddressFromSeed(seed, KeyVersion.MAINNET, CoinType.SEMUX); - bip44.getAddress(address, 0); + HdKeyPair key = bip44.getRootKeyPairFromSeed(seed, KeyVersion.MAINNET, CoinType.SEMUX); + bip44.getChildKeyPair(key, 0); } } From 8fd77cc1e55d5150183d631d11080a525d772ecb Mon Sep 17 00:00:00 2001 From: semux Date: Mon, 1 Jul 2019 22:22:57 +0100 Subject: [PATCH 4/7] Crypto: implement and test BIP32-ED25519 --- src/main/java/org/semux/core/Wallet.java | 4 +- .../java/org/semux/crypto/bip32/CoinType.java | 4 +- .../semux/crypto/bip32/HdKeyGenerator.java | 210 ++++++++++++------ .../org/semux/crypto/bip32/util/HdUtil.java | 2 - .../org/semux/crypto/bip32/util/Hmac.java | 65 ++++++ .../semux/crypto/bip32/util/HmacSha512.java | 44 ---- src/main/java/org/semux/gui/SwingUtil.java | 1 - .../org/semux/api/http/HttpHandlerTest.java | 1 - src/test/java/org/semux/core/WalletTest.java | 1 - .../java/org/semux/crypto/NativeTest.java | 4 +- .../semux/crypto/bip32/Bip32Ed25519Test.java | 86 +++++++ ...SlipVectorOneTest.java => Slip10Test.java} | 12 +- .../org/semux/crypto/bip44/Bip44Test.java | 2 +- .../org/semux/integration/SyncingTest.java | 1 - .../org/semux/integration/TransactTest.java | 1 - src/test/java/org/semux/util/IOUtilTest.java | 1 - 16 files changed, 301 insertions(+), 138 deletions(-) create mode 100644 src/main/java/org/semux/crypto/bip32/util/Hmac.java delete mode 100644 src/main/java/org/semux/crypto/bip32/util/HmacSha512.java create mode 100644 src/test/java/org/semux/crypto/bip32/Bip32Ed25519Test.java rename src/test/java/org/semux/crypto/bip32/{SlipVectorOneTest.java => Slip10Test.java} (94%) diff --git a/src/main/java/org/semux/core/Wallet.java b/src/main/java/org/semux/core/Wallet.java index ffe68e5c6..94dfdd3c8 100644 --- a/src/main/java/org/semux/core/Wallet.java +++ b/src/main/java/org/semux/core/Wallet.java @@ -689,7 +689,7 @@ public Key addAccountWithNextHdKey() { requireHdWalletInitialized(); synchronized (accounts) { - HdKeyPair rootKey = BIP_44.getRootKeyPairFromSeed(hdSeed, KeyVersion.MAINNET, CoinType.SEMUX); + HdKeyPair rootKey = BIP_44.getRootKeyPairFromSeed(hdSeed, KeyVersion.MAINNET, CoinType.SEMUX_SLIP10); HdKeyPair key = BIP_44.getChildKeyPair(rootKey, nextHdAccountIndex++); Key newKey = Key.fromRawPrivateKey(key.getPrivateKey().getKeyData()); ByteArray to = ByteArray.of(newKey.toAddress()); @@ -723,7 +723,7 @@ public int scanForHdKeys(AccountState accountState) { found++; } - HdKeyPair rootAddress = BIP_44.getRootKeyPairFromSeed(hdSeed, getKeyVersion(network), CoinType.SEMUX); + HdKeyPair rootAddress = BIP_44.getRootKeyPairFromSeed(hdSeed, getKeyVersion(network), CoinType.SEMUX_SLIP10); nextHdAccountIndex = 0; diff --git a/src/main/java/org/semux/crypto/bip32/CoinType.java b/src/main/java/org/semux/crypto/bip32/CoinType.java index 8c349be86..875150912 100644 --- a/src/main/java/org/semux/crypto/bip32/CoinType.java +++ b/src/main/java/org/semux/crypto/bip32/CoinType.java @@ -9,7 +9,9 @@ public enum CoinType { BITCOIN(Scheme.BIP32, 0, false), - SEMUX(Scheme.SLIP10_ED25519, 7562605, true); + SEMUX_SLIP10(Scheme.SLIP10_ED25519, 7562605, true), + + SEMUX(Scheme.BIP32_ED25519, 7562605, false); private final Scheme scheme; private final long coinType; diff --git a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java index 50738ca9b..b44ed55e7 100644 --- a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java +++ b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java @@ -7,19 +7,20 @@ package org.semux.crypto.bip32; -import java.io.UnsupportedEncodingException; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import org.apache.commons.lang3.ArrayUtils; import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.encoders.Hex; import org.semux.crypto.CryptoException; import org.semux.crypto.bip32.key.HdPrivateKey; import org.semux.crypto.bip32.key.HdPublicKey; import org.semux.crypto.bip32.key.KeyVersion; import org.semux.crypto.bip32.util.HashUtil; import org.semux.crypto.bip32.util.HdUtil; -import org.semux.crypto.bip32.util.HmacSha512; +import org.semux.crypto.bip32.util.Hmac; import org.semux.crypto.bip32.util.Secp256k1; import net.i2p.crypto.eddsa.EdDSAPrivateKey; @@ -31,6 +32,8 @@ public class HdKeyGenerator { + private static boolean trace = true; + private static final EdDSAParameterSpec ED25519SPEC = EdDSANamedCurveTable.getByName("ed25519"); public static final String MASTER_PATH = "m"; @@ -42,23 +45,36 @@ public HdKeyPair getMasterKeyPairFromSeed(byte[] seed, KeyVersion keyVersion, Co HdPrivateKey privateKey = new HdPrivateKey(); HdKeyPair key = new HdKeyPair(privateKey, publicKey, coinType, MASTER_PATH); - byte[] I; - try { - I = HmacSha512.hmac512(seed, scheme.getSeed().getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new CryptoException("Unable to decode seed."); - } - - // split into left/right + byte[] I = Hmac.hmac512(seed, scheme.getSeed().getBytes(StandardCharsets.UTF_8)); byte[] IL = Arrays.copyOfRange(I, 0, 32); byte[] IR = Arrays.copyOfRange(I, 32, 64); - BigInteger masterSecretKey = HdUtil.parse256(IL); - - // In case IL is 0 or >=n, the master key is invalid. - if (scheme != Scheme.SLIP10_ED25519 && masterSecretKey.compareTo(BigInteger.ZERO) == 0 - || masterSecretKey.compareTo(Secp256k1.getN()) > 0) { - throw new CryptoException("The master key is invalid"); + switch (scheme) { + case BIP32: + BigInteger masterSecretKey = HdUtil.parse256(IL); + // In case IL is 0 or >=n, the master key is invalid. + if (masterSecretKey.compareTo(BigInteger.ZERO) == 0 + || masterSecretKey.compareTo(Secp256k1.getN()) > 0) { + throw new CryptoException("The master key is invalid"); + } + case SLIP10_ED25519: + // Any IL would be fine + break; + case BIP32_ED25519: + // loop when the third highest bit of the last byte of IL is not zero + while ((I[31] & 0b00100000) != 0) { + I = Hmac.hmac512(I, scheme.getSeed().getBytes(StandardCharsets.UTF_8)); + } + // the lowest 3 bits of the first byte of IL of are cleared + I[0] = (byte) (I[0] & ~0b00000111); + // the highest bit of the last byte is cleared + I[31] = (byte) (I[31] & ~0b10000000); + // the second highest bit of the last byte is set + I[31] = (byte) (I[31] | 0b01000000); + + IL = Arrays.copyOfRange(I, 0, 32); + IR = Arrays.copyOfRange(I, 32, 64); + break; } privateKey.setVersion(keyVersion.getPrivateKeyVersion()); @@ -66,20 +82,35 @@ public HdKeyPair getMasterKeyPairFromSeed(byte[] seed, KeyVersion keyVersion, Co privateKey.setFingerprint(new byte[] { 0, 0, 0, 0 }); privateKey.setChildNumber(new byte[] { 0, 0, 0, 0 }); privateKey.setChainCode(IR); - privateKey.setKeyData(HdUtil.merge(new byte[] { 0 }, IL)); publicKey.setVersion(keyVersion.getPublicKeyVersion()); publicKey.setDepth(0); publicKey.setFingerprint(new byte[] { 0, 0, 0, 0 }); publicKey.setChildNumber(new byte[] { 0, 0, 0, 0 }); publicKey.setChainCode(IR); - publicKey.setKeyData(Secp256k1.serP(Secp256k1.point(masterSecretKey))); - if (scheme == Scheme.SLIP10_ED25519 || scheme == Scheme.BIP32_ED25519) { - privateKey.setKeyData(IL); + switch (scheme) { + case BIP32: + privateKey.setKeyData(HdUtil.merge(new byte[] { 0 }, IL)); + publicKey.setKeyData(Secp256k1.serP(Secp256k1.point(HdUtil.parse256(IL)))); + break; + case SLIP10_ED25519: EdDSAPrivateKey sk = new EdDSAPrivateKey(new EdDSAPrivateKeySpec(IL, ED25519SPEC)); EdDSAPublicKey pk = new EdDSAPublicKey(new EdDSAPublicKeySpec(sk.getA(), sk.getParams())); - publicKey.setKeyData(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); + privateKey.setKeyData(IL); // 32 bytes + publicKey.setKeyData(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); // 33 bytes + break; + case BIP32_ED25519: + // Attention: no-standard key format + byte[] A = ED25519SPEC.getB().scalarMultiply(IL).toByteArray(); + privateKey.setKeyData(I); // 64 bytes + publicKey.setKeyData(A); // 32 bytes? + + byte[] c = Hmac.hmac256(HdUtil.merge(new byte[] { 1 }, seed), + scheme.getSeed().getBytes(StandardCharsets.UTF_8)); + privateKey.setChainCode(c); + publicKey.setChainCode(c); + break; } return key; @@ -115,9 +146,9 @@ public HdPublicKey getChildPublicKey(HdPublicKey parent, long child, boolean isH byte[] key = parent.getKeyData(); byte[] data = HdUtil.merge(key, HdUtil.ser32(child)); - // I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)) - byte[] I = HmacSha512.hmac512(data, parent.getChainCode()); + // I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)) + byte[] I = Hmac.hmac512(data, parent.getChainCode()); byte[] IL = Arrays.copyOfRange(I, 0, 32); byte[] IR = Arrays.copyOfRange(I, 32, 64); @@ -151,7 +182,7 @@ public HdPublicKey getChildPublicKey(HdPublicKey parent, long child, boolean isH /** * Derive the child key pair (public + private) from the parent key pair. - * + * * @param parent * the parent key * @param child @@ -181,94 +212,120 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene // bytes long.) BigInteger kpar = HdUtil.parse256(parent.getPrivateKey().getKeyData()); byte[] data = HdUtil.merge(new byte[] { 0 }, HdUtil.ser256(kpar), HdUtil.ser32(child)); - I = HmacSha512.hmac512(data, xChain); + I = Hmac.hmac512(data, xChain); } else { // I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)) // just use public key byte[] data = HdUtil.merge(parent.getPublicKey().getKeyData(), HdUtil.ser32(child)); - I = HmacSha512.hmac512(data, xChain); + I = Hmac.hmac512(data, xChain); } - // split into left/right byte[] IL = Arrays.copyOfRange(I, 0, 32); byte[] IR = Arrays.copyOfRange(I, 32, 64); - // The returned child key ki is parse256(IL) + kpar (mod n). - BigInteger parse256 = HdUtil.parse256(IL); - BigInteger kpar = HdUtil.parse256(parent.getPrivateKey().getKeyData()); - BigInteger childSecretKey = parse256.add(kpar).mod(Secp256k1.getN()); byte[] childNumber = HdUtil.ser32(child); - byte[] fingerprint = HdUtil.getFingerprint(parent.getPrivateKey().getKeyData()); privateKey.setVersion(parent.getPrivateKey().getVersion()); privateKey.setDepth(parent.getPrivateKey().getDepth() + 1); - privateKey.setFingerprint(fingerprint); privateKey.setChildNumber(childNumber); privateKey.setChainCode(IR); - privateKey.setKeyData(HdUtil.merge(new byte[] { 0 }, HdUtil.ser256(childSecretKey))); - - ECPoint point = Secp256k1.point(childSecretKey); publicKey.setVersion(parent.getPublicKey().getVersion()); publicKey.setDepth(parent.getPublicKey().getDepth() + 1); - - // can just use fingerprint, but let's use data from parent public key - byte[] pKd = parent.getPublicKey().getKeyData(); - byte[] h160 = HashUtil.h160(pKd); - byte[] childFingerprint = new byte[] { h160[0], h160[1], h160[2], h160[3] }; - - publicKey.setFingerprint(childFingerprint); publicKey.setChildNumber(childNumber); publicKey.setChainCode(IR); - publicKey.setKeyData(Secp256k1.serP(point)); - Scheme scheme = parent.getCoinType().getScheme(); - if (scheme == Scheme.BIP32_ED25519) { - byte[] kLP = parent.getPrivateKey().getKeyData(); - byte[] kRP = parent.getPrivateKey().getChainCode(); - byte[] AP = Arrays.copyOfRange(parent.getPublicKey().getKeyData(), 1, 33); + switch (parent.getCoinType().getScheme()) { + case BIP32: + // The returned child key ki is parse256(IL) + kpar (mod n). + BigInteger parse256 = HdUtil.parse256(IL); + BigInteger kpar = HdUtil.parse256(parent.getPrivateKey().getKeyData()); + BigInteger childSecretKey = parse256.add(kpar).mod(Secp256k1.getN()); + + byte[] fingerprintSK = HdUtil.getFingerprint(parent.getPrivateKey().getKeyData()); + privateKey.setFingerprint(fingerprintSK); + privateKey.setKeyData(HdUtil.merge(new byte[] { 0 }, HdUtil.ser256(childSecretKey))); + + byte[] pKd = parent.getPublicKey().getKeyData(); + byte[] h160 = HashUtil.h160(pKd); + byte[] fingerprintPK = new byte[] { h160[0], h160[1], h160[2], h160[3] }; + publicKey.setFingerprint(fingerprintPK); + publicKey.setKeyData(Secp256k1.serP(Secp256k1.point(childSecretKey))); + break; + case SLIP10_ED25519: + // NOTE: fingerprints are the same for both HdPublicKey and HdPrivateKey? + h160 = HashUtil.h160(parent.getPublicKey().getKeyData()); + fingerprintPK = new byte[] { h160[0], h160[1], h160[2], h160[3] }; + + privateKey.setFingerprint(fingerprintPK); + privateKey.setKeyData(IL); - byte[] Z; + EdDSAPrivateKey sk = new EdDSAPrivateKey(new EdDSAPrivateKeySpec(IL, ED25519SPEC)); + EdDSAPublicKey pk = new EdDSAPublicKey(new EdDSAPublicKeySpec(sk.getA(), sk.getParams())); + publicKey.setFingerprint(fingerprintPK); + publicKey.setKeyData(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); + break; + case BIP32_ED25519: + byte[] kL = parent.getPrivateKey().getKeyData(); + byte[] kLP = Arrays.copyOfRange(kL, 0, 32); + byte[] kRP = Arrays.copyOfRange(kL, 32, 64); + byte[] AP = parent.getPublicKey().getKeyData(); + byte[] cP = parent.getPublicKey().getChainCode(); + + byte[] Z, c; if (isHardened) { byte[] data = HdUtil.merge(new byte[] { 0 }, kLP, kRP, HdUtil.ser32LE(child)); - Z = HmacSha512.hmac512(data, parent.getPrivateKey().getChainCode()); + Z = Hmac.hmac512(data, cP); + data[0] = 1; + c = Hmac.hmac512(data, cP); } else { byte[] data = HdUtil.merge(new byte[] { 2 }, AP, HdUtil.ser32LE(child)); - Z = HmacSha512.hmac512(data, parent.getPrivateKey().getChainCode()); + Z = Hmac.hmac512(data, cP); + data[0] = 3; + c = Hmac.hmac512(data, cP); } + c = Arrays.copyOfRange(c, 32, 64); byte[] ZL = Arrays.copyOfRange(Z, 0, 28); byte[] ZR = Arrays.copyOfRange(Z, 32, 64); - BigInteger kLiBI = parseBigIntegerLE(ZL) + + if (trace) { + System.out.println("parent, kLP = " + Hex.toHexString(kLP)); + System.out.println("parent, kRP = " + Hex.toHexString(kRP)); + System.out.println("parent, AP = " + Hex.toHexString(AP)); + System.out.println("parent, cP = " + Hex.toHexString(cP)); + } + + BigInteger kLiBI = parseUnsignedLE(ZL) .multiply(BigInteger.valueOf(8)) - .add(parseBigIntegerLE(kLP)); + .add(parseUnsignedLE(kLP)); BigInteger order = BigInteger.valueOf(2).pow(252) .add(new BigInteger("27742317777372353535851937790883648493")); if (kLiBI.mod(order).equals(BigInteger.ZERO)) { return null; } - byte[] kLi = serializeBigIntegerLE(kLiBI); - BigInteger kRiBI = parseBigIntegerLE(ZR) - .add(parseBigIntegerLE(kRP)) + IL = serializeUnsignedLE256(kLiBI); + + BigInteger kRiBI = parseUnsignedLE(ZR) + .add(parseUnsignedLE(kRP)) .mod(BigInteger.valueOf(2).pow(256)); - byte[] kRi = serializeBigIntegerLE(kRiBI); + IR = serializeUnsignedLE256(kRiBI); - IL = kLi; - IR = kRi; - } + I = HdUtil.merge(IL, IR); + byte[] A = ED25519SPEC.getB().scalarMultiply(IL).toByteArray(); - if (scheme == Scheme.SLIP10_ED25519 || scheme == Scheme.BIP32_ED25519) { - h160 = HashUtil.h160(parent.getPublicKey().getKeyData()); - childFingerprint = new byte[] { h160[0], h160[1], h160[2], h160[3] }; + privateKey.setKeyData(I); + publicKey.setKeyData(A); - privateKey.setFingerprint(childFingerprint); - privateKey.setChainCode(IR); - privateKey.setKeyData(IL); + privateKey.setChainCode(c); + publicKey.setChainCode(c); - EdDSAPrivateKey sk = new EdDSAPrivateKey(new EdDSAPrivateKeySpec(IL, ED25519SPEC)); - EdDSAPublicKey pk = new EdDSAPublicKey(new EdDSAPublicKeySpec(sk.getA(), sk.getParams())); - publicKey.setFingerprint(childFingerprint); - privateKey.setChainCode(IR); - publicKey.setKeyData(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); + if (trace) { + System.out.println("child, IL = " + Hex.toHexString(IL)); + System.out.println("child, IR = " + Hex.toHexString(IR)); + System.out.println("child, A = " + Hex.toHexString(A)); + System.out.println("child, c = " + Hex.toHexString(c)); + } + break; } return key; @@ -281,15 +338,20 @@ private String getPath(String parentPath, long child, boolean isHardened) { return parentPath + "/" + child + (isHardened ? "'" : ""); } - private BigInteger parseBigIntegerLE(byte[] bytes) { + private BigInteger parseUnsignedLE(byte[] bytes) { byte[] temp = bytes.clone(); ArrayUtils.reverse(temp); return new BigInteger(1, temp); } - private byte[] serializeBigIntegerLE(BigInteger bi) { + private byte[] serializeUnsignedLE256(BigInteger bi) { byte[] temp = bi.toByteArray(); + if (temp.length > 32) { + temp = Arrays.copyOfRange(temp, temp.length - 32, temp.length); + } + ArrayUtils.reverse(temp); + if (temp.length < 32) { return Arrays.copyOf(temp, 32); } else { diff --git a/src/main/java/org/semux/crypto/bip32/util/HdUtil.java b/src/main/java/org/semux/crypto/bip32/util/HdUtil.java index ea59e6609..9f49094a9 100644 --- a/src/main/java/org/semux/crypto/bip32/util/HdUtil.java +++ b/src/main/java/org/semux/crypto/bip32/util/HdUtil.java @@ -10,8 +10,6 @@ import java.util.Arrays; import java.util.stream.Stream; -import org.apache.commons.lang3.ArrayUtils; - /** * General Util class for defined functions. */ diff --git a/src/main/java/org/semux/crypto/bip32/util/Hmac.java b/src/main/java/org/semux/crypto/bip32/util/Hmac.java new file mode 100644 index 000000000..d35760f00 --- /dev/null +++ b/src/main/java/org/semux/crypto/bip32/util/Hmac.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2017-2018 The Semux Developers + * + * Distributed under the MIT software license, see the accompanying file + * LICENSE or https://opensource.org/licenses/mit-license.php + */ + +package org.semux.crypto.bip32.util; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.semux.crypto.CryptoException; + +/** + * Utility class for Hmac SHA-512 + */ +public class Hmac { + + private static final String HMAC_SHA256 = "HmacSHA256"; + private static final String HMAC_SHA512 = "HmacSHA512"; + + /** + * Returns the HmacSHA512 digest. + * + * @param message + * message + * @param secret + * secret + * @return a digest + */ + public static byte[] hmac256(byte[] message, byte[] secret) { + try { + Mac mac = Mac.getInstance(HMAC_SHA256); + SecretKeySpec keySpec = new SecretKeySpec(secret, HMAC_SHA256); + mac.init(keySpec); + return mac.doFinal(message); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new CryptoException("Unable to perform HmacSHA256.", e); + } + } + + /** + * Returns the HmacSHA512 digest. + * + * @param message + * message + * @param secret + * secret + * @return a digest + */ + public static byte[] hmac512(byte[] message, byte[] secret) { + try { + Mac mac = Mac.getInstance(HMAC_SHA512); + SecretKeySpec keySpec = new SecretKeySpec(secret, HMAC_SHA512); + mac.init(keySpec); + return mac.doFinal(message); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new CryptoException("Unable to perform HmacSHA512.", e); + } + } +} diff --git a/src/main/java/org/semux/crypto/bip32/util/HmacSha512.java b/src/main/java/org/semux/crypto/bip32/util/HmacSha512.java deleted file mode 100644 index 37ae848b4..000000000 --- a/src/main/java/org/semux/crypto/bip32/util/HmacSha512.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2017-2018 The Semux Developers - * - * Distributed under the MIT software license, see the accompanying file - * LICENSE or https://opensource.org/licenses/mit-license.php - */ - -package org.semux.crypto.bip32.util; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import org.semux.crypto.CryptoException; - -/** - * Utility class for Hmac SHA-512 - */ -public class HmacSha512 { - - private static final String HMAC_SHA512 = "HmacSHA512"; - - /** - * hmac512 - * - * @param key - * key - * @param seed - * seed - * @return hmac512 - */ - public static byte[] hmac512(byte[] key, byte[] seed) { - try { - Mac sha512_HMAC = Mac.getInstance(HMAC_SHA512); - SecretKeySpec keySpec = new SecretKeySpec(seed, HMAC_SHA512); - sha512_HMAC.init(keySpec); - return sha512_HMAC.doFinal(key); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new CryptoException("Unable to perform hmac512.", e); - } - } -} diff --git a/src/main/java/org/semux/gui/SwingUtil.java b/src/main/java/org/semux/gui/SwingUtil.java index 93abd1d3b..f81bf4005 100644 --- a/src/main/java/org/semux/gui/SwingUtil.java +++ b/src/main/java/org/semux/gui/SwingUtil.java @@ -7,7 +7,6 @@ package org.semux.gui; import static java.util.Arrays.stream; - import static org.semux.core.Amount.Unit.SEM; import static org.semux.gui.TextContextMenuItem.COPY; import static org.semux.gui.TextContextMenuItem.CUT; diff --git a/src/test/java/org/semux/api/http/HttpHandlerTest.java b/src/test/java/org/semux/api/http/HttpHandlerTest.java index 073e63625..e44e4ecd5 100644 --- a/src/test/java/org/semux/api/http/HttpHandlerTest.java +++ b/src/test/java/org/semux/api/http/HttpHandlerTest.java @@ -8,7 +8,6 @@ import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_OK; - import static junit.framework.TestCase.assertTrue; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/org/semux/core/WalletTest.java b/src/test/java/org/semux/core/WalletTest.java index fbdabd8d1..5c05aeffb 100644 --- a/src/test/java/org/semux/core/WalletTest.java +++ b/src/test/java/org/semux/core/WalletTest.java @@ -7,7 +7,6 @@ package org.semux.core; import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ; - import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/src/test/java/org/semux/crypto/NativeTest.java b/src/test/java/org/semux/crypto/NativeTest.java index 86f9f4b80..fd886f36d 100644 --- a/src/test/java/org/semux/crypto/NativeTest.java +++ b/src/test/java/org/semux/crypto/NativeTest.java @@ -408,7 +408,7 @@ public void benchmarkH160() { @Test public void benchmarkSign() { byte[] data = Bytes.random(512); - int repeat = 20_000; + int repeat = 5_000; Key key = new Key(); @@ -438,7 +438,7 @@ public void benchmarkSign() { @Test public void benchmarkVerify() { byte[] data = Bytes.random(512); - int repeat = 20_000; + int repeat = 5_000; Key key = new Key(); Key.Signature sig = key.sign(data); diff --git a/src/test/java/org/semux/crypto/bip32/Bip32Ed25519Test.java b/src/test/java/org/semux/crypto/bip32/Bip32Ed25519Test.java new file mode 100644 index 000000000..78dba8afb --- /dev/null +++ b/src/test/java/org/semux/crypto/bip32/Bip32Ed25519Test.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2017-2018 The Semux Developers + * + * Distributed under the MIT software license, see the accompanying file + * LICENSE or https://opensource.org/licenses/mit-license.php + */ +package org.semux.crypto.bip32; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.semux.crypto.Hex; +import org.semux.crypto.bip32.key.KeyVersion; +import org.semux.crypto.bip39.Language; +import org.semux.crypto.bip39.MnemonicGenerator; + +public class Bip32Ed25519Test { + + private byte[] SEED = new MnemonicGenerator().getSeedFromWordlist( + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "", + Language.ENGLISH); + + private HdKeyGenerator generator = new HdKeyGenerator(); + private HdKeyPair root = generator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); + + @Test + public void testRoot() { + System.out.println("k = " + Hex.encode(root.getPrivateKey().getKeyData())); + System.out.println("A = " + Hex.encode(root.getPublicKey().getKeyData())); + System.out.println("c = " + Hex.encode(root.getPublicKey().getChainCode())); + + assertEquals( + "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4", + Hex.encode(SEED)); + assertEquals("402b03cd9c8bed9ba9f9bd6cd9c315ce9fcc59c7c25d37c85a36096617e69d41" + + "8e35cb4a3b737afd007f0688618f21a8831643c0e6c77fc33c06026d2a0fc938", + Hex.encode(root.getPrivateKey().getKeyData())); + assertEquals("291ea7aa3766cd26a3a8688375aa07b3fed73c13d42543a9f19a48dc8b6bfd07", + Hex.encode(root.getPublicKey().getKeyData())); + assertEquals("32596435e70647d7d98ef102a32ea40319ca8fb6c851d7346d3bd8f9d1492658", + Hex.encode(root.getPublicKey().getChainCode())); + } + + @Test + public void testOne() { + // path = "42'/1/2"; + String kL = "b02160bb753c495687eb0b0e0628bf637e85fd3aadac109847afa2ad20e69d41"; + String kR = "00ea111776aabeb85446b186110f8337a758681c96d5d01d5f42d34baf97087b"; + String A = "bc738b13faa157ce8f1534ddd9299e458be459f734a5fa17d1f0e73f559a69ee"; + String c = "c52916b7bb856bd1733390301cdc22fd2b0d5e6fab9908d55fd1bed13bccbb36"; + + HdKeyPair child1 = generator.getChildKeyPair(root, 42, true); + HdKeyPair child2 = generator.getChildKeyPair(child1, 1, false); + HdKeyPair child3 = generator.getChildKeyPair(child2, 2, false); + + System.out.println("k = " + Hex.encode(child3.getPrivateKey().getKeyData())); + System.out.println("A = " + Hex.encode(child3.getPublicKey().getKeyData())); + System.out.println("c = " + Hex.encode(child3.getPublicKey().getChainCode())); + + assertEquals(kL + kR, Hex.encode(child3.getPrivateKey().getKeyData())); + assertEquals(A, Hex.encode(child3.getPublicKey().getKeyData())); + assertEquals(c, Hex.encode(child3.getPublicKey().getChainCode())); + } + + @Test + public void testTwo() { + // path = "42'/3'/5"; + String kL = "78164270a17f697b57f172a7ac58cfbb95e007fdcd968c8c6a2468841fe69d41"; + String kR = "15c846a5d003f7017374d12105c25930a2bf8c386b7be3c470d8226f3cad8b6b"; + String A = "286b8d4ef3321e78ecd8e2585e45cb3a8c97d3f11f829860ce461df992a7f51c"; + String c = "7e64c416800883256828efc63567d8842eda422c413f5ff191512dfce7790984"; + + HdKeyPair child1 = generator.getChildKeyPair(root, 42, true); + HdKeyPair child2 = generator.getChildKeyPair(child1, 3, true); + HdKeyPair child3 = generator.getChildKeyPair(child2, 5, false); + + System.out.println("k = " + Hex.encode(child3.getPrivateKey().getKeyData())); + System.out.println("A = " + Hex.encode(child3.getPublicKey().getKeyData())); + System.out.println("c = " + Hex.encode(child3.getPublicKey().getChainCode())); + + assertEquals(kL + kR, Hex.encode(child3.getPrivateKey().getKeyData())); + assertEquals(A, Hex.encode(child3.getPublicKey().getKeyData())); + assertEquals(c, Hex.encode(child3.getPublicKey().getChainCode())); + } +} diff --git a/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java b/src/test/java/org/semux/crypto/bip32/Slip10Test.java similarity index 94% rename from src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java rename to src/test/java/org/semux/crypto/bip32/Slip10Test.java index eab9e234e..6cfe2fe09 100644 --- a/src/test/java/org/semux/crypto/bip32/SlipVectorOneTest.java +++ b/src/test/java/org/semux/crypto/bip32/Slip10Test.java @@ -12,7 +12,7 @@ import org.semux.crypto.Hex; import org.semux.crypto.bip32.key.KeyVersion; -public class SlipVectorOneTest { +public class Slip10Test { public static final byte[] SEED = Hex.decode("000102030405060708090a0b0c0d0e0f"); public HdKeyGenerator hdKeyGenerator = new HdKeyGenerator(); @@ -24,7 +24,7 @@ public void testVectorOneMaster() { byte[] privateKey = Hex.decode("2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7"); byte[] publicKey = Hex.decode("00a4b2856bfec510abab89753fac1ac0e1112364e7d250545963f135f2a33188ed"); - HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); + HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX_SLIP10); Assert.assertArrayEquals(fingerprint, master.getPrivateKey().getFingerprint()); Assert.assertArrayEquals(chainCode, master.getPrivateKey().getChainCode()); @@ -35,7 +35,7 @@ public void testVectorOneMaster() { @Test(expected = UnsupportedOperationException.class) public void testUnableToPublicChain() { - HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); + HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX_SLIP10); hdKeyGenerator.getChildPublicKey(master.getPublicKey(), 0, false, Scheme.SLIP10_ED25519); } @@ -46,7 +46,7 @@ public void testVector0H() { byte[] privateKey = Hex.decode("68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3"); byte[] publicKey = Hex.decode("008c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c"); - HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); + HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX_SLIP10); HdKeyPair child = hdKeyGenerator.getChildKeyPair(master, 0, true); Assert.assertArrayEquals(fingerprint, child.getPrivateKey().getFingerprint()); @@ -63,7 +63,7 @@ public void testVector0H1H() { byte[] privateKey = Hex.decode("b1d0bad404bf35da785a64ca1ac54b2617211d2777696fbffaf208f746ae84f2"); byte[] publicKey = Hex.decode("001932a5270f335bed617d5b935c80aedb1a35bd9fc1e31acafd5372c30f5c1187"); - HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); + HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX_SLIP10); HdKeyPair child = hdKeyGenerator.getChildKeyPair(master, 0, true); child = hdKeyGenerator.getChildKeyPair(child, 1, true); @@ -81,7 +81,7 @@ public void testVector0H1H2H() { byte[] privateKey = Hex.decode("92a5b23c0b8a99e37d07df3fb9966917f5d06e02ddbd909c7e184371463e9fc9"); byte[] publicKey = Hex.decode("00ae98736566d30ed0e9d2f4486a64bc95740d89c7db33f52121f8ea8f76ff0fc1"); - HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX); + HdKeyPair master = hdKeyGenerator.getMasterKeyPairFromSeed(SEED, KeyVersion.MAINNET, CoinType.SEMUX_SLIP10); HdKeyPair child = hdKeyGenerator.getChildKeyPair(master, 0, true); child = hdKeyGenerator.getChildKeyPair(child, 1, true); child = hdKeyGenerator.getChildKeyPair(child, 2, true); diff --git a/src/test/java/org/semux/crypto/bip44/Bip44Test.java b/src/test/java/org/semux/crypto/bip44/Bip44Test.java index 034a1be59..11866cc87 100644 --- a/src/test/java/org/semux/crypto/bip44/Bip44Test.java +++ b/src/test/java/org/semux/crypto/bip44/Bip44Test.java @@ -25,7 +25,7 @@ public void testBitcoin() { @Test public void testSemux() { - HdKeyPair key = bip44.getRootKeyPairFromSeed(seed, KeyVersion.MAINNET, CoinType.SEMUX); + HdKeyPair key = bip44.getRootKeyPairFromSeed(seed, KeyVersion.MAINNET, CoinType.SEMUX_SLIP10); bip44.getChildKeyPair(key, 0); } } diff --git a/src/test/java/org/semux/integration/SyncingTest.java b/src/test/java/org/semux/integration/SyncingTest.java index c472a4ece..720fc0f0c 100644 --- a/src/test/java/org/semux/integration/SyncingTest.java +++ b/src/test/java/org/semux/integration/SyncingTest.java @@ -7,7 +7,6 @@ package org.semux.integration; import static java.util.concurrent.TimeUnit.SECONDS; - import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertTrue; import static org.semux.core.Amount.Unit.SEM; diff --git a/src/test/java/org/semux/integration/TransactTest.java b/src/test/java/org/semux/integration/TransactTest.java index a9a4fa3a9..b406ce57a 100644 --- a/src/test/java/org/semux/integration/TransactTest.java +++ b/src/test/java/org/semux/integration/TransactTest.java @@ -7,7 +7,6 @@ package org.semux.integration; import static java.util.concurrent.TimeUnit.SECONDS; - import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.equalTo; diff --git a/src/test/java/org/semux/util/IOUtilTest.java b/src/test/java/org/semux/util/IOUtilTest.java index 5dfb8bff1..98d8890e2 100644 --- a/src/test/java/org/semux/util/IOUtilTest.java +++ b/src/test/java/org/semux/util/IOUtilTest.java @@ -7,7 +7,6 @@ package org.semux.util; import static java.nio.charset.StandardCharsets.UTF_8; - import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; From 5e69872e431a28ff285e8b02b6c8c2e80cc924a9 Mon Sep 17 00:00:00 2001 From: semux Date: Mon, 1 Jul 2019 22:35:12 +0100 Subject: [PATCH 5/7] Trivial: remove dependency on commons-lang3 in crypto --- .../org/semux/core/TransactionResult.java | 4 +- src/main/java/org/semux/crypto/Key.java | 14 +--- .../semux/crypto/bip32/HdKeyGenerator.java | 73 ++++++++++--------- .../util/{HdUtil.java => BytesUtil.java} | 4 +- 4 files changed, 46 insertions(+), 49 deletions(-) rename src/main/java/org/semux/crypto/bip32/util/{HdUtil.java => BytesUtil.java} (96%) diff --git a/src/main/java/org/semux/core/TransactionResult.java b/src/main/java/org/semux/core/TransactionResult.java index 123a99ed2..5ae391317 100644 --- a/src/main/java/org/semux/core/TransactionResult.java +++ b/src/main/java/org/semux/core/TransactionResult.java @@ -10,12 +10,12 @@ import java.util.ArrayList; import java.util.List; -import org.bouncycastle.util.encoders.Hex; import org.ethereum.vm.DataWord; import org.ethereum.vm.LogInfo; import org.ethereum.vm.OpCode; import org.ethereum.vm.program.InternalTransaction; import org.semux.Network; +import org.semux.crypto.Hex; import org.semux.util.Bytes; import org.semux.util.SimpleDecoder; import org.semux.util.SimpleEncoder; @@ -419,7 +419,7 @@ public byte[] toBytesForMerkle() { @Override public String toString() { - return "TransactionResult [code=" + code + ", returnData=" + Hex.toHexString(returnData) + ", # logs=" + return "TransactionResult [code=" + code + ", returnData=" + Hex.encode(returnData) + ", # logs=" + logs.size() + "]"; } } diff --git a/src/main/java/org/semux/crypto/Key.java b/src/main/java/org/semux/crypto/Key.java index 37929f283..5672c76ef 100644 --- a/src/main/java/org/semux/crypto/Key.java +++ b/src/main/java/org/semux/crypto/Key.java @@ -16,8 +16,6 @@ import java.util.Arrays; import java.util.Collection; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; import org.semux.crypto.cache.PublicKeyCache; import org.semux.util.Bytes; import org.semux.util.SystemUtil; @@ -346,20 +344,12 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; - Signature signature = (Signature) o; - - return new EqualsBuilder() - .append(s, signature.s) - .append(a, signature.a) - .isEquals(); + return this.toBytes().equals(((Signature) o).toBytes()); } @Override public int hashCode() { - return new HashCodeBuilder(17, 37) - .append(s) - .append(a) - .toHashCode(); + return Arrays.hashCode(toBytes()); } } diff --git a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java index b44ed55e7..016648764 100644 --- a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java +++ b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java @@ -11,15 +11,14 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; -import org.apache.commons.lang3.ArrayUtils; import org.bouncycastle.math.ec.ECPoint; -import org.bouncycastle.util.encoders.Hex; import org.semux.crypto.CryptoException; +import org.semux.crypto.Hex; import org.semux.crypto.bip32.key.HdPrivateKey; import org.semux.crypto.bip32.key.HdPublicKey; import org.semux.crypto.bip32.key.KeyVersion; import org.semux.crypto.bip32.util.HashUtil; -import org.semux.crypto.bip32.util.HdUtil; +import org.semux.crypto.bip32.util.BytesUtil; import org.semux.crypto.bip32.util.Hmac; import org.semux.crypto.bip32.util.Secp256k1; @@ -51,7 +50,7 @@ public HdKeyPair getMasterKeyPairFromSeed(byte[] seed, KeyVersion keyVersion, Co switch (scheme) { case BIP32: - BigInteger masterSecretKey = HdUtil.parse256(IL); + BigInteger masterSecretKey = BytesUtil.parse256(IL); // In case IL is 0 or >=n, the master key is invalid. if (masterSecretKey.compareTo(BigInteger.ZERO) == 0 || masterSecretKey.compareTo(Secp256k1.getN()) > 0) { @@ -91,14 +90,14 @@ public HdKeyPair getMasterKeyPairFromSeed(byte[] seed, KeyVersion keyVersion, Co switch (scheme) { case BIP32: - privateKey.setKeyData(HdUtil.merge(new byte[] { 0 }, IL)); - publicKey.setKeyData(Secp256k1.serP(Secp256k1.point(HdUtil.parse256(IL)))); + privateKey.setKeyData(BytesUtil.merge(new byte[] { 0 }, IL)); + publicKey.setKeyData(Secp256k1.serP(Secp256k1.point(BytesUtil.parse256(IL)))); break; case SLIP10_ED25519: EdDSAPrivateKey sk = new EdDSAPrivateKey(new EdDSAPrivateKeySpec(IL, ED25519SPEC)); EdDSAPublicKey pk = new EdDSAPublicKey(new EdDSAPublicKeySpec(sk.getA(), sk.getParams())); privateKey.setKeyData(IL); // 32 bytes - publicKey.setKeyData(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); // 33 bytes + publicKey.setKeyData(BytesUtil.merge(new byte[] { 0 }, pk.getAbyte())); // 33 bytes break; case BIP32_ED25519: // Attention: no-standard key format @@ -106,7 +105,7 @@ public HdKeyPair getMasterKeyPairFromSeed(byte[] seed, KeyVersion keyVersion, Co privateKey.setKeyData(I); // 64 bytes publicKey.setKeyData(A); // 32 bytes? - byte[] c = Hmac.hmac256(HdUtil.merge(new byte[] { 1 }, seed), + byte[] c = Hmac.hmac256(BytesUtil.merge(new byte[] { 1 }, seed), scheme.getSeed().getBytes(StandardCharsets.UTF_8)); privateKey.setChainCode(c); publicKey.setChainCode(c); @@ -145,7 +144,7 @@ public HdPublicKey getChildPublicKey(HdPublicKey parent, long child, boolean isH } byte[] key = parent.getKeyData(); - byte[] data = HdUtil.merge(key, HdUtil.ser32(child)); + byte[] data = BytesUtil.merge(key, BytesUtil.ser32(child)); // I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)) byte[] I = Hmac.hmac512(data, parent.getChainCode()); @@ -161,7 +160,7 @@ public HdPublicKey getChildPublicKey(HdPublicKey parent, long child, boolean isH byte[] h160 = HashUtil.h160(pKd); byte[] childFingerprint = new byte[] { h160[0], h160[1], h160[2], h160[3] }; - BigInteger ILBigInt = HdUtil.parse256(IL); + BigInteger ILBigInt = BytesUtil.parse256(IL); ECPoint point = Secp256k1.point(ILBigInt); point = point.add(Secp256k1.deserP(parent.getKeyData())); @@ -173,7 +172,7 @@ public HdPublicKey getChildPublicKey(HdPublicKey parent, long child, boolean isH byte[] childKey = Secp256k1.serP(point); publicKey.setFingerprint(childFingerprint); - publicKey.setChildNumber(HdUtil.ser32(child)); + publicKey.setChildNumber(BytesUtil.ser32(child)); publicKey.setChainCode(IR); publicKey.setKeyData(childKey); @@ -210,20 +209,20 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene // If so (hardened child): let I = HMAC-SHA512(Key = cpar, Data = 0x00 || // ser256(kpar) || ser32(i)). (Note: The 0x00 pads the private key to make it 33 // bytes long.) - BigInteger kpar = HdUtil.parse256(parent.getPrivateKey().getKeyData()); - byte[] data = HdUtil.merge(new byte[] { 0 }, HdUtil.ser256(kpar), HdUtil.ser32(child)); + BigInteger kpar = BytesUtil.parse256(parent.getPrivateKey().getKeyData()); + byte[] data = BytesUtil.merge(new byte[] { 0 }, BytesUtil.ser256(kpar), BytesUtil.ser32(child)); I = Hmac.hmac512(data, xChain); } else { // I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)) // just use public key - byte[] data = HdUtil.merge(parent.getPublicKey().getKeyData(), HdUtil.ser32(child)); + byte[] data = BytesUtil.merge(parent.getPublicKey().getKeyData(), BytesUtil.ser32(child)); I = Hmac.hmac512(data, xChain); } // split into left/right byte[] IL = Arrays.copyOfRange(I, 0, 32); byte[] IR = Arrays.copyOfRange(I, 32, 64); - byte[] childNumber = HdUtil.ser32(child); + byte[] childNumber = BytesUtil.ser32(child); privateKey.setVersion(parent.getPrivateKey().getVersion()); privateKey.setDepth(parent.getPrivateKey().getDepth() + 1); @@ -238,13 +237,13 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene switch (parent.getCoinType().getScheme()) { case BIP32: // The returned child key ki is parse256(IL) + kpar (mod n). - BigInteger parse256 = HdUtil.parse256(IL); - BigInteger kpar = HdUtil.parse256(parent.getPrivateKey().getKeyData()); + BigInteger parse256 = BytesUtil.parse256(IL); + BigInteger kpar = BytesUtil.parse256(parent.getPrivateKey().getKeyData()); BigInteger childSecretKey = parse256.add(kpar).mod(Secp256k1.getN()); - byte[] fingerprintSK = HdUtil.getFingerprint(parent.getPrivateKey().getKeyData()); + byte[] fingerprintSK = BytesUtil.getFingerprint(parent.getPrivateKey().getKeyData()); privateKey.setFingerprint(fingerprintSK); - privateKey.setKeyData(HdUtil.merge(new byte[] { 0 }, HdUtil.ser256(childSecretKey))); + privateKey.setKeyData(BytesUtil.merge(new byte[] { 0 }, BytesUtil.ser256(childSecretKey))); byte[] pKd = parent.getPublicKey().getKeyData(); byte[] h160 = HashUtil.h160(pKd); @@ -263,7 +262,7 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene EdDSAPrivateKey sk = new EdDSAPrivateKey(new EdDSAPrivateKeySpec(IL, ED25519SPEC)); EdDSAPublicKey pk = new EdDSAPublicKey(new EdDSAPublicKeySpec(sk.getA(), sk.getParams())); publicKey.setFingerprint(fingerprintPK); - publicKey.setKeyData(HdUtil.merge(new byte[] { 0 }, pk.getAbyte())); + publicKey.setKeyData(BytesUtil.merge(new byte[] { 0 }, pk.getAbyte())); break; case BIP32_ED25519: byte[] kL = parent.getPrivateKey().getKeyData(); @@ -274,12 +273,12 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene byte[] Z, c; if (isHardened) { - byte[] data = HdUtil.merge(new byte[] { 0 }, kLP, kRP, HdUtil.ser32LE(child)); + byte[] data = BytesUtil.merge(new byte[] { 0 }, kLP, kRP, BytesUtil.ser32LE(child)); Z = Hmac.hmac512(data, cP); data[0] = 1; c = Hmac.hmac512(data, cP); } else { - byte[] data = HdUtil.merge(new byte[] { 2 }, AP, HdUtil.ser32LE(child)); + byte[] data = BytesUtil.merge(new byte[] { 2 }, AP, BytesUtil.ser32LE(child)); Z = Hmac.hmac512(data, cP); data[0] = 3; c = Hmac.hmac512(data, cP); @@ -289,10 +288,10 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene byte[] ZR = Arrays.copyOfRange(Z, 32, 64); if (trace) { - System.out.println("parent, kLP = " + Hex.toHexString(kLP)); - System.out.println("parent, kRP = " + Hex.toHexString(kRP)); - System.out.println("parent, AP = " + Hex.toHexString(AP)); - System.out.println("parent, cP = " + Hex.toHexString(cP)); + System.out.println("parent, kLP = " + Hex.encode(kLP)); + System.out.println("parent, kRP = " + Hex.encode(kRP)); + System.out.println("parent, AP = " + Hex.encode(AP)); + System.out.println("parent, cP = " + Hex.encode(cP)); } BigInteger kLiBI = parseUnsignedLE(ZL) @@ -310,7 +309,7 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene .mod(BigInteger.valueOf(2).pow(256)); IR = serializeUnsignedLE256(kRiBI); - I = HdUtil.merge(IL, IR); + I = BytesUtil.merge(IL, IR); byte[] A = ED25519SPEC.getB().scalarMultiply(IL).toByteArray(); privateKey.setKeyData(I); @@ -320,10 +319,10 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene publicKey.setChainCode(c); if (trace) { - System.out.println("child, IL = " + Hex.toHexString(IL)); - System.out.println("child, IR = " + Hex.toHexString(IR)); - System.out.println("child, A = " + Hex.toHexString(A)); - System.out.println("child, c = " + Hex.toHexString(c)); + System.out.println("child, IL = " + Hex.encode(IL)); + System.out.println("child, IR = " + Hex.encode(IR)); + System.out.println("child, A = " + Hex.encode(A)); + System.out.println("child, c = " + Hex.encode(c)); } break; } @@ -338,9 +337,17 @@ private String getPath(String parentPath, long child, boolean isHardened) { return parentPath + "/" + child + (isHardened ? "'" : ""); } + private void reverse(byte[] input) { + for (int i = 0; i < input.length / 2; i++) { + byte temp = input[i]; + input[i] = input[input.length - 1 - i]; + input[input.length - 1 - i] = temp; + } + } + private BigInteger parseUnsignedLE(byte[] bytes) { byte[] temp = bytes.clone(); - ArrayUtils.reverse(temp); + reverse(temp); return new BigInteger(1, temp); } @@ -350,7 +357,7 @@ private byte[] serializeUnsignedLE256(BigInteger bi) { temp = Arrays.copyOfRange(temp, temp.length - 32, temp.length); } - ArrayUtils.reverse(temp); + reverse(temp); if (temp.length < 32) { return Arrays.copyOf(temp, 32); diff --git a/src/main/java/org/semux/crypto/bip32/util/HdUtil.java b/src/main/java/org/semux/crypto/bip32/util/BytesUtil.java similarity index 96% rename from src/main/java/org/semux/crypto/bip32/util/HdUtil.java rename to src/main/java/org/semux/crypto/bip32/util/BytesUtil.java index 9f49094a9..7e772d006 100644 --- a/src/main/java/org/semux/crypto/bip32/util/HdUtil.java +++ b/src/main/java/org/semux/crypto/bip32/util/BytesUtil.java @@ -13,7 +13,7 @@ /** * General Util class for defined functions. */ -public class HdUtil { +public class BytesUtil { /** * ser32(i): serialize a 32-bit unsigned integer i as a 4-byte sequence, most @@ -129,7 +129,7 @@ public static byte[] merge(byte[]... arrays) { * @return fingerprint */ public static byte[] getFingerprint(byte[] keyData) { - byte[] point = Secp256k1.serP(Secp256k1.point(org.semux.crypto.bip32.util.HdUtil.parse256(keyData))); + byte[] point = Secp256k1.serP(Secp256k1.point(BytesUtil.parse256(keyData))); byte[] h160 = HashUtil.h160(point); return new byte[] { h160[0], h160[1], h160[2], h160[3] }; } From 50bbd1b46ad5be058353e3c9f3fa22669855ad8c Mon Sep 17 00:00:00 2001 From: semux Date: Mon, 1 Jul 2019 23:17:53 +0100 Subject: [PATCH 6/7] Misc: fix regression issues --- pom.xml | 15 ++++++++++----- src/main/java/org/semux/crypto/Key.java | 2 +- .../org/semux/crypto/bip32/HdKeyGenerator.java | 2 +- src/main/java/org/semux/gui/SwingUtil.java | 1 + src/test/java/org/semux/TestLoggingAppender.java | 5 +++-- .../java/org/semux/api/http/HttpHandlerTest.java | 1 + src/test/java/org/semux/cli/SemuxCliTest.java | 2 +- src/test/java/org/semux/core/WalletTest.java | 1 + .../java/org/semux/integration/SyncingTest.java | 1 + .../java/org/semux/integration/TransactTest.java | 1 + src/test/java/org/semux/util/IOUtilTest.java | 1 + 11 files changed, 22 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index afa08a9b1..950da26f2 100644 --- a/pom.xml +++ b/pom.xml @@ -607,7 +607,7 @@ javax.activation activation - 1.1.1 + 1.1 javax.xml.bind @@ -624,7 +624,7 @@ org.slf4j slf4j-api - 1.7.26 + 1.7.25 org.apache.logging.log4j @@ -736,6 +736,11 @@ jackson-databind 2.9.9 + + com.fasterxml.jackson.core + jackson-annotations + 2.9.0 + @@ -782,19 +787,19 @@ org.hamcrest hamcrest-core - 2.1 + 1.3 test org.hamcrest hamcrest-library - 2.1 + 1.3 test org.assertj assertj-core - 3.12.2 + 3.11.1 test diff --git a/src/main/java/org/semux/crypto/Key.java b/src/main/java/org/semux/crypto/Key.java index 5672c76ef..1bb1efbf3 100644 --- a/src/main/java/org/semux/crypto/Key.java +++ b/src/main/java/org/semux/crypto/Key.java @@ -344,7 +344,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; - return this.toBytes().equals(((Signature) o).toBytes()); + return Arrays.equals(toBytes(), ((Signature) o).toBytes()); } @Override diff --git a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java index 016648764..2d6825b2e 100644 --- a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java +++ b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java @@ -17,8 +17,8 @@ import org.semux.crypto.bip32.key.HdPrivateKey; import org.semux.crypto.bip32.key.HdPublicKey; import org.semux.crypto.bip32.key.KeyVersion; -import org.semux.crypto.bip32.util.HashUtil; import org.semux.crypto.bip32.util.BytesUtil; +import org.semux.crypto.bip32.util.HashUtil; import org.semux.crypto.bip32.util.Hmac; import org.semux.crypto.bip32.util.Secp256k1; diff --git a/src/main/java/org/semux/gui/SwingUtil.java b/src/main/java/org/semux/gui/SwingUtil.java index f81bf4005..93abd1d3b 100644 --- a/src/main/java/org/semux/gui/SwingUtil.java +++ b/src/main/java/org/semux/gui/SwingUtil.java @@ -7,6 +7,7 @@ package org.semux.gui; import static java.util.Arrays.stream; + import static org.semux.core.Amount.Unit.SEM; import static org.semux.gui.TextContextMenuItem.COPY; import static org.semux.gui.TextContextMenuItem.CUT; diff --git a/src/test/java/org/semux/TestLoggingAppender.java b/src/test/java/org/semux/TestLoggingAppender.java index bd61aff1c..005f6c653 100644 --- a/src/test/java/org/semux/TestLoggingAppender.java +++ b/src/test/java/org/semux/TestLoggingAppender.java @@ -16,6 +16,7 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginElement; @@ -36,12 +37,12 @@ public final class TestLoggingAppender extends AbstractAppender { protected TestLoggingAppender(String name, Filter filter, Layout layout) { - super(name, filter, layout); + super(name, filter, layout, true, Property.EMPTY_ARRAY); } protected TestLoggingAppender(String name, Filter filter, Layout layout, boolean ignoreExceptions) { - super(name, filter, layout, ignoreExceptions); + super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY); } public static void prepare(Level level) { diff --git a/src/test/java/org/semux/api/http/HttpHandlerTest.java b/src/test/java/org/semux/api/http/HttpHandlerTest.java index e44e4ecd5..073e63625 100644 --- a/src/test/java/org/semux/api/http/HttpHandlerTest.java +++ b/src/test/java/org/semux/api/http/HttpHandlerTest.java @@ -8,6 +8,7 @@ import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_OK; + import static junit.framework.TestCase.assertTrue; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/org/semux/cli/SemuxCliTest.java b/src/test/java/org/semux/cli/SemuxCliTest.java index 8ce6a23f1..c9a3d0161 100644 --- a/src/test/java/org/semux/cli/SemuxCliTest.java +++ b/src/test/java/org/semux/cli/SemuxCliTest.java @@ -6,7 +6,7 @@ */ package org.semux.cli; -import static org.hamcrest.core.IsCollectionContaining.hasItem; +import static org.hamcrest.CoreMatchers.hasItem; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; diff --git a/src/test/java/org/semux/core/WalletTest.java b/src/test/java/org/semux/core/WalletTest.java index 5c05aeffb..fbdabd8d1 100644 --- a/src/test/java/org/semux/core/WalletTest.java +++ b/src/test/java/org/semux/core/WalletTest.java @@ -7,6 +7,7 @@ package org.semux.core; import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ; + import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/src/test/java/org/semux/integration/SyncingTest.java b/src/test/java/org/semux/integration/SyncingTest.java index 720fc0f0c..c472a4ece 100644 --- a/src/test/java/org/semux/integration/SyncingTest.java +++ b/src/test/java/org/semux/integration/SyncingTest.java @@ -7,6 +7,7 @@ package org.semux.integration; import static java.util.concurrent.TimeUnit.SECONDS; + import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertTrue; import static org.semux.core.Amount.Unit.SEM; diff --git a/src/test/java/org/semux/integration/TransactTest.java b/src/test/java/org/semux/integration/TransactTest.java index b406ce57a..a9a4fa3a9 100644 --- a/src/test/java/org/semux/integration/TransactTest.java +++ b/src/test/java/org/semux/integration/TransactTest.java @@ -7,6 +7,7 @@ package org.semux.integration; import static java.util.concurrent.TimeUnit.SECONDS; + import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.equalTo; diff --git a/src/test/java/org/semux/util/IOUtilTest.java b/src/test/java/org/semux/util/IOUtilTest.java index 98d8890e2..5dfb8bff1 100644 --- a/src/test/java/org/semux/util/IOUtilTest.java +++ b/src/test/java/org/semux/util/IOUtilTest.java @@ -7,6 +7,7 @@ package org.semux.util; import static java.nio.charset.StandardCharsets.UTF_8; + import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; From 118e1e4ae0badc554c1cd5e4861f47d77bff16fb Mon Sep 17 00:00:00 2001 From: semux Date: Tue, 2 Jul 2019 09:33:11 +0100 Subject: [PATCH 7/7] Trivial: replace systemm.out with logger --- .../semux/crypto/bip32/HdKeyGenerator.java | 31 +++++++------ .../semux/crypto/bip32/Bip32Ed25519Test.java | 44 ++++++++++--------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java index 2d6825b2e..14122d233 100644 --- a/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java +++ b/src/main/java/org/semux/crypto/bip32/HdKeyGenerator.java @@ -21,6 +21,8 @@ import org.semux.crypto.bip32.util.HashUtil; import org.semux.crypto.bip32.util.Hmac; import org.semux.crypto.bip32.util.Secp256k1; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; @@ -31,7 +33,7 @@ public class HdKeyGenerator { - private static boolean trace = true; + private static final Logger logger = LoggerFactory.getLogger(HdKeyGenerator.class); private static final EdDSAParameterSpec ED25519SPEC = EdDSANamedCurveTable.getByName("ed25519"); @@ -265,9 +267,9 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene publicKey.setKeyData(BytesUtil.merge(new byte[] { 0 }, pk.getAbyte())); break; case BIP32_ED25519: - byte[] kL = parent.getPrivateKey().getKeyData(); - byte[] kLP = Arrays.copyOfRange(kL, 0, 32); - byte[] kRP = Arrays.copyOfRange(kL, 32, 64); + byte[] kP = parent.getPrivateKey().getKeyData(); + byte[] kLP = Arrays.copyOfRange(kP, 0, 32); + byte[] kRP = Arrays.copyOfRange(kP, 32, 64); byte[] AP = parent.getPublicKey().getKeyData(); byte[] cP = parent.getPublicKey().getChainCode(); @@ -287,11 +289,11 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene byte[] ZL = Arrays.copyOfRange(Z, 0, 28); byte[] ZR = Arrays.copyOfRange(Z, 32, 64); - if (trace) { - System.out.println("parent, kLP = " + Hex.encode(kLP)); - System.out.println("parent, kRP = " + Hex.encode(kRP)); - System.out.println("parent, AP = " + Hex.encode(AP)); - System.out.println("parent, cP = " + Hex.encode(cP)); + if (logger.isTraceEnabled()) { + logger.trace("parent, kLP = " + Hex.encode(kLP)); + logger.trace("parent, kRP = " + Hex.encode(kRP)); + logger.trace("parent, AP = " + Hex.encode(AP)); + logger.trace("parent, cP = " + Hex.encode(cP)); } BigInteger kLiBI = parseUnsignedLE(ZL) @@ -318,12 +320,13 @@ public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardene privateKey.setChainCode(c); publicKey.setChainCode(c); - if (trace) { - System.out.println("child, IL = " + Hex.encode(IL)); - System.out.println("child, IR = " + Hex.encode(IR)); - System.out.println("child, A = " + Hex.encode(A)); - System.out.println("child, c = " + Hex.encode(c)); + if (logger.isTraceEnabled()) { + logger.trace("child, IL = " + Hex.encode(IL)); + logger.trace("child, IR = " + Hex.encode(IR)); + logger.trace("child, A = " + Hex.encode(A)); + logger.trace("child, c = " + Hex.encode(c)); } + break; } diff --git a/src/test/java/org/semux/crypto/bip32/Bip32Ed25519Test.java b/src/test/java/org/semux/crypto/bip32/Bip32Ed25519Test.java index 78dba8afb..0f7ba2ce6 100644 --- a/src/test/java/org/semux/crypto/bip32/Bip32Ed25519Test.java +++ b/src/test/java/org/semux/crypto/bip32/Bip32Ed25519Test.java @@ -13,9 +13,13 @@ import org.semux.crypto.bip32.key.KeyVersion; import org.semux.crypto.bip39.Language; import org.semux.crypto.bip39.MnemonicGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Bip32Ed25519Test { + private static final Logger logger = LoggerFactory.getLogger(Bip32Ed25519Test.class); + private byte[] SEED = new MnemonicGenerator().getSeedFromWordlist( "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", "", @@ -26,20 +30,20 @@ public class Bip32Ed25519Test { @Test public void testRoot() { - System.out.println("k = " + Hex.encode(root.getPrivateKey().getKeyData())); - System.out.println("A = " + Hex.encode(root.getPublicKey().getKeyData())); - System.out.println("c = " + Hex.encode(root.getPublicKey().getChainCode())); - - assertEquals( - "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4", - Hex.encode(SEED)); - assertEquals("402b03cd9c8bed9ba9f9bd6cd9c315ce9fcc59c7c25d37c85a36096617e69d41" - + "8e35cb4a3b737afd007f0688618f21a8831643c0e6c77fc33c06026d2a0fc938", - Hex.encode(root.getPrivateKey().getKeyData())); - assertEquals("291ea7aa3766cd26a3a8688375aa07b3fed73c13d42543a9f19a48dc8b6bfd07", - Hex.encode(root.getPublicKey().getKeyData())); - assertEquals("32596435e70647d7d98ef102a32ea40319ca8fb6c851d7346d3bd8f9d1492658", - Hex.encode(root.getPublicKey().getChainCode())); + String seed = "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4"; + String kL = "402b03cd9c8bed9ba9f9bd6cd9c315ce9fcc59c7c25d37c85a36096617e69d41"; + String kR = "8e35cb4a3b737afd007f0688618f21a8831643c0e6c77fc33c06026d2a0fc938"; + String A = "291ea7aa3766cd26a3a8688375aa07b3fed73c13d42543a9f19a48dc8b6bfd07"; + String c = "32596435e70647d7d98ef102a32ea40319ca8fb6c851d7346d3bd8f9d1492658"; + + logger.info("k = " + Hex.encode(root.getPrivateKey().getKeyData())); + logger.info("A = " + Hex.encode(root.getPublicKey().getKeyData())); + logger.info("c = " + Hex.encode(root.getPublicKey().getChainCode())); + + assertEquals(seed, Hex.encode(SEED)); + assertEquals(kL + kR, Hex.encode(root.getPrivateKey().getKeyData())); + assertEquals(A, Hex.encode(root.getPublicKey().getKeyData())); + assertEquals(c, Hex.encode(root.getPublicKey().getChainCode())); } @Test @@ -54,9 +58,9 @@ public void testOne() { HdKeyPair child2 = generator.getChildKeyPair(child1, 1, false); HdKeyPair child3 = generator.getChildKeyPair(child2, 2, false); - System.out.println("k = " + Hex.encode(child3.getPrivateKey().getKeyData())); - System.out.println("A = " + Hex.encode(child3.getPublicKey().getKeyData())); - System.out.println("c = " + Hex.encode(child3.getPublicKey().getChainCode())); + logger.info("k = " + Hex.encode(child3.getPrivateKey().getKeyData())); + logger.info("A = " + Hex.encode(child3.getPublicKey().getKeyData())); + logger.info("c = " + Hex.encode(child3.getPublicKey().getChainCode())); assertEquals(kL + kR, Hex.encode(child3.getPrivateKey().getKeyData())); assertEquals(A, Hex.encode(child3.getPublicKey().getKeyData())); @@ -75,9 +79,9 @@ public void testTwo() { HdKeyPair child2 = generator.getChildKeyPair(child1, 3, true); HdKeyPair child3 = generator.getChildKeyPair(child2, 5, false); - System.out.println("k = " + Hex.encode(child3.getPrivateKey().getKeyData())); - System.out.println("A = " + Hex.encode(child3.getPublicKey().getKeyData())); - System.out.println("c = " + Hex.encode(child3.getPublicKey().getChainCode())); + logger.info("k = " + Hex.encode(child3.getPrivateKey().getKeyData())); + logger.info("A = " + Hex.encode(child3.getPublicKey().getKeyData())); + logger.info("c = " + Hex.encode(child3.getPublicKey().getChainCode())); assertEquals(kL + kR, Hex.encode(child3.getPrivateKey().getKeyData())); assertEquals(A, Hex.encode(child3.getPublicKey().getKeyData()));