diff --git a/core/pom.xml b/core/pom.xml index 52483db3..da191cf4 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -155,7 +155,7 @@ com.google.protobuf:protobuf-java:2.5.0:jar:null:compile:a10732c76bfacdbd633a7eb0f7968b1059a65dfa com.h2database:h2:1.3.167:jar:null:compile:d3867d586f087e53eb12fc65e5693d8ee9a5da17 com.lambdaworks:scrypt:1.3.3:jar:null:compile:06d6813de41e177189e1722717979b4fb5454b1d - com.madgag:sc-light-jdk15on:1.47.0.2:jar:null:compile:d5c98671cc97fa0d928be1c7eb5edd3fb95d3234 + com.madgag.spongycastle:core:1.50.0.0:jar:null:compile:13e93b00ec9790315debd61fa25ab6a47d3a1c52 net.jcip:jcip-annotations:1.0:jar:null:compile:afba4942caaeaf46aab0b976afd57cc7c181467e org.slf4j:slf4j-api:1.7.6:jar:null:compile:562424e36df3d2327e8e9301a76027fca17d54ea org.slf4j:slf4j-jdk14:1.7.6:jar:null:runtime:1a3301a32ea7d90c3d33e9d60edbfdc9589fc748 @@ -242,9 +242,9 @@ true - com.madgag - sc-light-jdk15on - 1.47.0.2 + com.madgag.spongycastle + core + 1.50.0.0 com.google.protobuf diff --git a/core/src/main/java/com/google/bitcoin/core/ECKey.java b/core/src/main/java/com/google/bitcoin/core/ECKey.java index 2e74e41e..8be0b479 100644 --- a/core/src/main/java/com/google/bitcoin/core/ECKey.java +++ b/core/src/main/java/com/google/bitcoin/core/ECKey.java @@ -30,9 +30,11 @@ import org.spongycastle.asn1.sec.SECNamedCurves; import org.spongycastle.asn1.x9.X9ECParameters; import org.spongycastle.asn1.x9.X9IntegerConverter; import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.digests.SHA256Digest; import org.spongycastle.crypto.generators.ECKeyPairGenerator; import org.spongycastle.crypto.params.*; import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.crypto.signers.HMacDSAKCalculator; import org.spongycastle.math.ec.ECAlgorithms; import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECPoint; @@ -125,19 +127,11 @@ public class ECKey implements Serializable { ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate(); ECPublicKeyParameters pubParams = (ECPublicKeyParameters) keypair.getPublic(); priv = privParams.getD(); - // Unfortunately Bouncy Castle does not let us explicitly change a point to be compressed, even though it - // could easily do so. We must re-build it here so the ECPoints withCompression flag can be set to true. - ECPoint uncompressed = pubParams.getQ(); - ECPoint compressed = compressPoint(uncompressed); - pub = compressed.getEncoded(); + pub = pubParams.getQ().getEncoded(true); creationTimeSeconds = Utils.currentTimeSeconds(); } - private static ECPoint compressPoint(ECPoint uncompressed) { - return new ECPoint.Fp(CURVE.getCurve(), uncompressed.getX(), uncompressed.getY(), true); - } - /** * Construct an ECKey from an ASN.1 encoded private key. These are produced by OpenSSL and stored by the Bitcoin * reference implementation in its wallet. Note that this is slow because it requires an EC point multiply. @@ -252,9 +246,7 @@ public class ECKey implements Serializable { */ public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean compressed) { ECPoint point = CURVE.getG().multiply(privKey); - if (compressed) - point = compressPoint(point); - return point.getEncoded(); + return point.getEncoded(compressed); } /** Gets the hash160 form of the public key (as seen in addresses). */ @@ -485,7 +477,7 @@ public class ECKey implements Serializable { } } - ECDSASigner signer = new ECDSASigner(); + ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, CURVE); signer.init(true, privKey); BigInteger[] components = signer.generateSignature(input.getBytes()); @@ -781,11 +773,7 @@ public class ECKey implements Serializable { BigInteger srInv = rInv.multiply(sig.s).mod(n); BigInteger eInvrInv = rInv.multiply(eInv).mod(n); ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE.getG(), eInvrInv, R, srInv); - if (compressed) { - // We have to manually recompress the point as the compressed-ness gets lost when multiply() is used. - q = new ECPoint.Fp(curve, q.getX(), q.getY(), true); - } - return new ECKey((byte[])null, q.getEncoded()); + return new ECKey((byte[])null, q.getEncoded(compressed)); } /** Decompress a compressed public key (x co-ord and low-bit of y-coord). */ diff --git a/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java b/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java index 1f6f1f69..540eb1cc 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java +++ b/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java @@ -61,7 +61,7 @@ public class DeterministicKey implements Serializable { this.parent = parent; this.childNumberPath = childNumberPath; this.chainCode = Arrays.copyOf(chainCode, chainCode.length); - this.publicAsPoint = publicAsPoint == null ? null : HDUtils.compressedCopy(publicAsPoint); + this.publicAsPoint = publicAsPoint == null ? null : publicAsPoint.normalize(); this.privateAsFieldElement = privateKeyFieldElt; } @@ -109,13 +109,13 @@ public class DeterministicKey implements Serializable { ECPoint getPubPoint() { if (publicAsPoint == null) { checkNotNull(privateAsFieldElement); - publicAsPoint = ECKey.CURVE.getG().multiply(privateAsFieldElement); + publicAsPoint = ECKey.CURVE.getG().multiply(privateAsFieldElement).normalize(); } - return HDUtils.compressedCopy(publicAsPoint); + return publicAsPoint; } public byte[] getPubKeyBytes() { - return getPubPoint().getEncoded(); + return getPubPoint().getEncoded(true); } diff --git a/core/src/main/java/com/google/bitcoin/crypto/HDKeyDerivation.java b/core/src/main/java/com/google/bitcoin/crypto/HDKeyDerivation.java index 15280e18..7164495c 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/HDKeyDerivation.java +++ b/core/src/main/java/com/google/bitcoin/crypto/HDKeyDerivation.java @@ -127,9 +127,8 @@ public final class HDKeyDerivation { } else { checkArgument(!childNumber.isPrivateDerivation(), "Can't use private derivation with public keys only."); ECPoint Ki = ECKey.CURVE.getG().multiply(ilInt).add(parent.getPubPoint()); - checkArgument(!Ki.equals(ECKey.CURVE.getCurve().getInfinity()), - "Illegal derived key: derived public key equals infinity."); - keyBytes = HDUtils.toCompressed(Ki.getEncoded()); + checkArgument(!Ki.isInfinity(), "Illegal derived key: derived public key equals infinity."); + keyBytes = Ki.getEncoded(true); } return new RawKeyBytes(keyBytes, chainCode); } diff --git a/core/src/main/java/com/google/bitcoin/crypto/HDUtils.java b/core/src/main/java/com/google/bitcoin/crypto/HDUtils.java index d7cdb9dd..99cb051a 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/HDUtils.java +++ b/core/src/main/java/com/google/bitcoin/crypto/HDUtils.java @@ -52,16 +52,8 @@ public final class HDUtils { return hmacSha512(createHmacSha512Digest(key), data); } - static ECPoint compressedCopy(ECPoint pubKPoint) { - return ECKey.CURVE.getCurve().createPoint(pubKPoint.getX().toBigInteger(), pubKPoint.getY().toBigInteger(), true); - } - - static ECPoint toUncompressed(ECPoint pubKPoint) { - return ECKey.CURVE.getCurve().createPoint(pubKPoint.getX().toBigInteger(), pubKPoint.getY().toBigInteger(), false); - } - static byte[] toCompressed(byte[] uncompressedPoint) { - return compressedCopy(ECKey.CURVE.getCurve().decodePoint(uncompressedPoint)).getEncoded(); + return ECKey.CURVE.getCurve().decodePoint(uncompressedPoint).getEncoded(true); } static byte[] longTo4ByteArray(long n) { @@ -71,7 +63,7 @@ public final class HDUtils { } static byte[] getBytes(ECPoint pubKPoint) { - return compressedCopy(pubKPoint).getEncoded(); + return pubKPoint.getEncoded(true); } static ImmutableList append(ImmutableList path, ChildNumber childNumber) { diff --git a/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java b/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java index c66b91b5..8781ad6e 100644 --- a/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java +++ b/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java @@ -37,6 +37,7 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.util.encoders.DecoderException; import org.spongycastle.util.encoders.Hex; import java.io.InputStream; @@ -424,8 +425,11 @@ public class ECKeyTest { sig.append((char)c); try { - assertFalse(TransactionSignature.isEncodingCanonical(Hex.decode(sig.toString()))); - } catch (StringIndexOutOfBoundsException e) { } // Expected for non-hex strings in the JSON that we should ignore + final String sigStr = sig.toString(); + assertFalse(TransactionSignature.isEncodingCanonical(Hex.decode(sigStr))); + } catch (DecoderException e) { + // Expected for non-hex strings in the JSON that we should ignore + } } in.close(); } diff --git a/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java b/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java index 7c025373..40f3be3c 100644 --- a/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java +++ b/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java @@ -1617,7 +1617,8 @@ public class FullBlockTestGenerator { // (finally) return the created chain return ret; } - + + private byte uniquenessCounter = 0; private Block createNextBlock(Block baseBlock, int nextBlockHeight, TransactionOutPointWithValue prevOut, BigInteger additionalCoinbaseValue) throws ScriptException { Integer height = blockToHeightMap.get(baseBlock.getHash()); @@ -1634,7 +1635,7 @@ public class FullBlockTestGenerator { t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), ScriptBuilder.createOutputScript(new ECKey(null, coinbaseOutKeyPubKey)).getProgram())); // Spendable output - t.addOutput(new TransactionOutput(params, t, BigInteger.ZERO, new byte[] {OP_1})); + t.addOutput(new TransactionOutput(params, t, BigInteger.ZERO, new byte[] {OP_1, uniquenessCounter++})); addOnlyInputToTransaction(t, prevOut); block.addTransaction(t); block.solve(); diff --git a/core/src/test/java/com/google/bitcoin/core/WalletTest.java b/core/src/test/java/com/google/bitcoin/core/WalletTest.java index ac276e7c..b682f096 100644 --- a/core/src/test/java/com/google/bitcoin/core/WalletTest.java +++ b/core/src/test/java/com/google/bitcoin/core/WalletTest.java @@ -19,8 +19,6 @@ package com.google.bitcoin.core; import com.google.bitcoin.core.Transaction.SigHash; import com.google.bitcoin.core.Wallet.SendRequest; -import com.google.bitcoin.wallet.DefaultCoinSelector; -import com.google.bitcoin.wallet.RiskAnalysis; import com.google.bitcoin.crypto.KeyCrypter; import com.google.bitcoin.crypto.KeyCrypterException; import com.google.bitcoin.crypto.KeyCrypterScrypt; @@ -30,9 +28,7 @@ import com.google.bitcoin.utils.MockTransactionBroadcaster; import com.google.bitcoin.utils.TestUtils; import com.google.bitcoin.utils.TestWithWallet; import com.google.bitcoin.utils.Threading; -import com.google.bitcoin.wallet.KeyTimeCoinSelector; -import com.google.bitcoin.wallet.WalletFiles; -import com.google.bitcoin.wallet.WalletTransaction; +import com.google.bitcoin.wallet.*; import com.google.bitcoin.wallet.WalletTransaction.Pool; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; @@ -692,12 +688,12 @@ public class WalletTest extends TestWithWallet { final BigInteger value2 = Utils.toNanoCoins(2, 0); // Give us three coins and make sure we have some change. sendMoneyToWallet(value.add(value2), AbstractBlockChain.NewBlockType.BEST_CHAIN); - // The two transactions will have different hashes due to the lack of deterministic signing, but will be - // otherwise identical. Once deterministic signatures are implemented, this test will have to be tweaked. final Address address = new ECKey().toAddress(params); Transaction send1 = checkNotNull(wallet.createSend(address, value2)); Transaction send2 = checkNotNull(wallet.createSend(address, value2)); - send1 = roundTripTransaction(params, send1); + byte[] buf = send1.bitcoinSerialize(); + buf[43] = 0; // Break the signature: bitcoinj won't check in SPV mode and this is easier than other mutations. + send1 = new Transaction(params, buf); wallet.commitTx(send2); wallet.allowSpendingUnconfirmedTransactions(); assertEquals(value, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); diff --git a/core/src/test/java/com/google/bitcoin/crypto/HDUtilsTest.java b/core/src/test/java/com/google/bitcoin/crypto/HDUtilsTest.java index ce43ab89..9dd8c85a 100644 --- a/core/src/test/java/com/google/bitcoin/crypto/HDUtilsTest.java +++ b/core/src/test/java/com/google/bitcoin/crypto/HDUtilsTest.java @@ -10,7 +10,6 @@ import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECPoint; import org.spongycastle.util.encoders.Hex; -import java.math.BigInteger; import java.util.Arrays; import java.util.List; @@ -126,14 +125,11 @@ public class HDUtilsTest { for (String testpkStr : testPubKey) { byte[] testpk = Hex.decode(testpkStr); - BigInteger pubX = new BigInteger(1, Arrays.copyOfRange(testpk, 1, 33)); - BigInteger pubY = new BigInteger(1, Arrays.copyOfRange(testpk, 33, 65)); - - ECPoint ptFlat = curve.createPoint(pubX, pubY, false); // 65 - ECPoint ptComp = curve.createPoint(pubX, pubY, true); // 33 - ECPoint uncompressed = HDUtils.toUncompressed(ptComp); - ECPoint recompressed = HDUtils.compressedCopy(uncompressed); ECPoint orig = curve.decodePoint(testpk); + ECPoint ptFlat = curve.decodePoint(orig.getEncoded(false)); + ECPoint ptComp = curve.decodePoint(ptFlat.getEncoded(true)); + ECPoint uncompressed = curve.decodePoint(ptComp.getEncoded(false)); + ECPoint recompressed = curve.decodePoint(uncompressed.getEncoded(true)); log.info("===================="); log.info("Flat: {}", asHexStr(ptFlat));