Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,25 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
java: [ '8', '11', '15' ]
# windows-latest --> no leveldbjni64-1.8 in java.library.path
os: [ubuntu-latest, macOS-latest]
java: [ '8', '11', '17' ]
fail-fast: false
name: JAVA ${{ matrix.java }} OS ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v1
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@v1
uses: gradle/gradle-build-action@v3
with:
gradle-version: 6.9
gradle-version: 7.4.2
arguments: build --stacktrace
- name: Upload Test Results and Reports
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
if: always()
with:
name: bitcoinj-core-test-results-jdk${{ matrix.java }}-${{ matrix.os }}
Expand Down
15 changes: 11 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import org.gradle.util.GradleVersion

buildscript {
repositories {
jcenter()
mavenCentral()
}
// If using Gradle 7, use the compatible protobuf plugin, else use the one that works with oldest supported Gradle
boolean isGradle7 = GradleVersion.current().compareTo(GradleVersion.version("7.0")) >= 0
def gradleProtobufVersion = isGradle7 ? "0.8.18" : "0.8.10"
if (isGradle7) {
System.err.println "Warning: Using com.google.protobuf:protobuf-gradle-plugin:${gradleProtobufVersion} because ${GradleVersion.current()}"
}

dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
classpath "com.google.protobuf:protobuf-gradle-plugin:${gradleProtobufVersion}"
}
}

allprojects {
repositories {
jcenter()
mavenCentral()
}

group = 'org.bitcoinj'
Expand Down
36 changes: 25 additions & 11 deletions core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
apply plugin: 'java'
apply plugin: 'com.google.protobuf'
apply plugin: 'maven'
apply plugin: 'eclipse'
import org.gradle.util.GradleVersion

version = '0.15.10.bisq.16'
plugins {
id 'java-library'
id 'com.google.protobuf'
id 'maven-publish'
id 'eclipse'
}

version = '0.15.11.bisq.16'
archivesBaseName = 'bitcoinj-core'
eclipse.project.name = 'bitcoinj-core'

dependencies {
compile 'org.bouncycastle:bcprov-jdk15to18:1.63'
implementation 'com.google.guava:guava:28.2-android'
compile 'com.google.protobuf:protobuf-java:3.6.1'
api 'org.bouncycastle:bcprov-jdk15to18:1.67'
api 'com.google.guava:guava:28.2-android'
api 'com.google.protobuf:protobuf-java:3.6.1'
implementation 'com.squareup.okhttp3:okhttp:3.12.8'
implementation 'org.slf4j:slf4j-api:1.7.30'
implementation 'net.jcip:jcip-annotations:1.0'
Expand All @@ -23,7 +27,7 @@ dependencies {
testImplementation 'org.fusesource.leveldbjni:leveldbjni-all:1.8'
}

sourceCompatibility = 1.7
sourceCompatibility = 1.8
compileJava.options.encoding = 'UTF-8'
compileTestJava.options.encoding = 'UTF-8'
javadoc.options.encoding = 'UTF-8'
Expand All @@ -46,13 +50,23 @@ test {
}
}

def minGradleArchiveClassifierVersion = GradleVersion.version("5.0")

task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
if (GradleVersion.current().compareTo(minGradleArchiveClassifierVersion) > 0) {
archiveClassifier.set('javadoc')
} else {
classifier = 'javadoc'
}
from javadoc.destinationDir
}

task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
if (GradleVersion.current().compareTo(minGradleArchiveClassifierVersion) > 0) {
archiveClassifier.set('sources')
} else {
classifier = 'sources'
}
from sourceSets.main.allSource
}

Expand Down
90 changes: 44 additions & 46 deletions core/src/main/java/org/bitcoinj/core/ECKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@

import static com.google.common.base.Preconditions.*;

// TODO: Move this class to tracking compression state itself.
// The Bouncy Castle developers are deprecating their own tracking of the compression state.

/**
* <p>Represents an elliptic curve public and (optionally) private key, usable for digital signatures but not encryption.
* Creating a new ECKey with the empty constructor will generate a new random keypair. Other static methods can be used
Expand Down Expand Up @@ -180,12 +177,12 @@ public ECKey(SecureRandom secureRandom) {
ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate();
ECPublicKeyParameters pubParams = (ECPublicKeyParameters) keypair.getPublic();
priv = privParams.getD();
pub = new LazyECPoint(CURVE.getCurve(), pubParams.getQ().getEncoded(true));
pub = getPointWithCompression(pubParams.getQ(), true);
creationTimeSeconds = Utils.currentTimeSeconds();
}

protected ECKey(@Nullable BigInteger priv, ECPoint pub) {
this(priv, new LazyECPoint(checkNotNull(pub)));
protected ECKey(@Nullable BigInteger priv, ECPoint pub, boolean compressed) {
this(priv, getPointWithCompression(checkNotNull(pub), compressed));
}

protected ECKey(@Nullable BigInteger priv, LazyECPoint pub) {
Expand All @@ -205,33 +202,20 @@ protected ECKey(@Nullable BigInteger priv, LazyECPoint pub) {
* Utility for compressing an elliptic curve point. Returns the same point if it's already compressed.
* See the ECKey class docs for a discussion of point compression.
*/
public static ECPoint compressPoint(ECPoint point) {
return getPointWithCompression(point, true);
}

public static LazyECPoint compressPoint(LazyECPoint point) {
return point.isCompressed() ? point : new LazyECPoint(compressPoint(point.get()));
return point.isCompressed() ? point : getPointWithCompression(point.get(), true);
}

/**
* Utility for decompressing an elliptic curve point. Returns the same point if it's already compressed.
* Utility for decompressing an elliptic curve point. Returns the same point if it's already uncompressed.
* See the ECKey class docs for a discussion of point compression.
*/
public static ECPoint decompressPoint(ECPoint point) {
return getPointWithCompression(point, false);
}

public static LazyECPoint decompressPoint(LazyECPoint point) {
return !point.isCompressed() ? point : new LazyECPoint(decompressPoint(point.get()));
return !point.isCompressed() ? point : getPointWithCompression(point.get(), false);
}

private static ECPoint getPointWithCompression(ECPoint point, boolean compressed) {
if (point.isCompressed() == compressed)
return point;
point = point.normalize();
BigInteger x = point.getAffineXCoord().toBigInteger();
BigInteger y = point.getAffineYCoord().toBigInteger();
return CURVE.getCurve().createPoint(x, y, compressed);
private static LazyECPoint getPointWithCompression(ECPoint point, boolean compressed) {
return new LazyECPoint(point, compressed);
}

/**
Expand All @@ -251,8 +235,8 @@ public static ECKey fromPrivate(BigInteger privKey) {
}

/**
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow), either
* compressed or not.
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow).
* @param compressed Determines whether the resulting ECKey will use a compressed encoding for the public key.
*/
public static ECKey fromPrivate(BigInteger privKey, boolean compressed) {
ECPoint point = publicPointFromPrivate(privKey);
Expand All @@ -268,8 +252,8 @@ public static ECKey fromPrivate(byte[] privKeyBytes) {
}

/**
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow), either
* compressed or not.
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow).
* @param compressed Determines whether the resulting ECKey will use a compressed encoding for the public key.
*/
public static ECKey fromPrivate(byte[] privKeyBytes, boolean compressed) {
return fromPrivate(new BigInteger(1, privKeyBytes), compressed);
Expand All @@ -278,10 +262,11 @@ public static ECKey fromPrivate(byte[] privKeyBytes, boolean compressed) {
/**
* Creates an ECKey that simply trusts the caller to ensure that point is really the result of multiplying the
* generator point by the private key. This is used to speed things up when you know you have the right values
* already. The compression state of pub will be preserved.
* already.
* @param compressed Determines whether the resulting ECKey will use a compressed encoding for the public key.
*/
public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, ECPoint pub) {
return new ECKey(priv, pub);
public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, ECPoint pub, boolean compressed) {
return new ECKey(priv, pub, compressed);
}

/**
Expand All @@ -292,23 +277,27 @@ public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, ECPoint p
public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] pub) {
checkNotNull(priv);
checkNotNull(pub);
return new ECKey(new BigInteger(1, priv), CURVE.getCurve().decodePoint(pub));
return new ECKey(new BigInteger(1, priv), new LazyECPoint(CURVE.getCurve(), pub));
}

/**
* Creates an ECKey that cannot be used for signing, only verifying signatures, from the given point. The
* compression state of pub will be preserved.
* Creates an ECKey that cannot be used for signing, only verifying signatures, from the given point.
* @param compressed Determines whether the resulting ECKey will use a compressed encoding for the public key.
*/
public static ECKey fromPublicOnly(ECPoint pub) {
return new ECKey(null, pub);
public static ECKey fromPublicOnly(ECPoint pub, boolean compressed) {
return new ECKey(null, pub, compressed);
}

/**
* Creates an ECKey that cannot be used for signing, only verifying signatures, from the given encoded point.
* The compression state of pub will be preserved.
*/
public static ECKey fromPublicOnly(byte[] pub) {
return new ECKey(null, CURVE.getCurve().decodePoint(pub));
return new ECKey(null, new LazyECPoint(CURVE.getCurve(), pub));
}

public static ECKey fromPublicOnly(ECKey key) {
return fromPublicOnly(key.getPubKeyPoint(), key.isCompressed());
}

/**
Expand All @@ -319,7 +308,7 @@ public ECKey decompress() {
if (!pub.isCompressed())
return this;
else
return new ECKey(priv, decompressPoint(pub.get()));
return new ECKey(priv, getPointWithCompression(pub.get(), false));
}

/**
Expand Down Expand Up @@ -374,8 +363,7 @@ public ECKey(@Nullable BigInteger privKey, @Nullable byte[] pubKey, boolean comp
if (pubKey == null) {
// Derive public from private.
ECPoint point = publicPointFromPrivate(privKey);
point = getPointWithCompression(point, compressed);
this.pub = new LazyECPoint(point);
this.pub = getPointWithCompression(point, compressed);
} else {
// We expect the pubkey to be in regular encoded form, just as a BigInteger. Therefore the first byte is
// a special marker byte.
Expand Down Expand Up @@ -807,6 +795,18 @@ public static boolean isPubKeyCanonical(byte[] pubkey) {
return true;
}

/**
* Returns true if the given pubkey is in its compressed form.
*/
public static boolean isPubKeyCompressed(byte[] encoded) {
if (encoded.length == 33 && (encoded[0] == 0x02 || encoded[0] == 0x03))
return true;
else if (encoded.length == 65 && encoded[0] == 0x04)
return false;
else
throw new IllegalArgumentException(Utils.HEX.encode(encoded));
}

private static ECKey extractKeyFromASN1(byte[] asn1privkey) {
// To understand this code, see the definition of the ASN.1 format for EC private keys in the OpenSSL source
// code in ec_asn1.c:
Expand Down Expand Up @@ -841,8 +841,8 @@ private static ECKey extractKeyFromASN1(byte[] asn1privkey) {
checkArgument(encoding >= 2 && encoding <= 4, "Input has 'publicKey' with invalid encoding");

// Now sanity check to ensure the pubkey bytes match the privkey.
boolean compressed = (pubbits.length == 33);
ECKey key = new ECKey(privkey, null, compressed);
boolean compressed = isPubKeyCompressed(pubbits);
ECKey key = new ECKey(privkey, (byte[]) null, compressed);
if (!Arrays.equals(key.getPubKey(), pubbits))
throw new IllegalArgumentException("Public key in ASN.1 structure does not match private key.");
return key;
Expand Down Expand Up @@ -1025,7 +1025,7 @@ public static ECKey recoverFromSignature(int recId, ECDSASignature sig, Sha256Ha
BigInteger srInv = rInv.multiply(sig.s).mod(n);
BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
ECPoint q = ECAlgorithms.sumOfTwoMultiplies(CURVE.getG(), eInvrInv, R, srInv);
return ECKey.fromPublicOnly(q.getEncoded(compressed));
return ECKey.fromPublicOnly(q, compressed);
}

/** Decompress a compressed public key (x co-ord and low-bit of y-coord). */
Expand Down Expand Up @@ -1110,9 +1110,7 @@ public ECKey decrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypt
if (unencryptedPrivateKey.length != 32)
throw new KeyCrypterException.InvalidCipherText(
"Decrypted key must be 32 bytes long, but is " + unencryptedPrivateKey.length);
ECKey key = ECKey.fromPrivate(unencryptedPrivateKey);
if (!isCompressed())
key = key.decompress();
ECKey key = ECKey.fromPrivate(unencryptedPrivateKey, isCompressed());
if (!Arrays.equals(key.getPubKey(), getPubKey()))
throw new KeyCrypterException("Provided AES key is wrong");
key.setCreationTimeSeconds(creationTimeSeconds);
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/org/bitcoinj/core/VersionMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
public class VersionMessage extends Message {

/** The version of this library release, as a string. */
public static final String BITCOINJ_VERSION = "0.15.10.bisq.16";
public static final String BITCOINJ_VERSION = "0.15.11.bisq.16";
/** The value that is prepended to the subVer field of this application. */
public static final String LIBRARY_SUBVER = "/bitcoinj:" + BITCOINJ_VERSION + "/";

Expand Down
9 changes: 5 additions & 4 deletions core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,18 @@ public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
ECPoint publicAsPoint,
boolean compressed,
@Nullable BigInteger priv,
@Nullable DeterministicKey parent) {
this(childNumberPath, chainCode, new LazyECPoint(publicAsPoint), priv, parent);
this(childNumberPath, chainCode, new LazyECPoint(publicAsPoint, compressed), priv, parent);
}

/** Constructs a key from its components. This is not normally something you should use. */
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
BigInteger priv,
@Nullable DeterministicKey parent) {
super(priv, compressPoint(ECKey.publicPointFromPrivate(priv)));
super(priv, ECKey.publicPointFromPrivate(priv), true);
checkArgument(chainCode.length == 32);
this.parent = parent;
this.childNumberPath = checkNotNull(childNumberPath);
Expand Down Expand Up @@ -157,7 +158,7 @@ public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
@Nullable DeterministicKey parent,
int depth,
int parentFingerprint) {
super(priv, compressPoint(ECKey.publicPointFromPrivate(priv)));
super(priv, ECKey.publicPointFromPrivate(priv), true);
checkArgument(chainCode.length == 32);
this.parent = parent;
this.childNumberPath = checkNotNull(childNumberPath);
Expand All @@ -169,7 +170,7 @@ public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,

/** Clones the key */
public DeterministicKey(DeterministicKey keyToClone, DeterministicKey newParent) {
super(keyToClone.priv, keyToClone.pub.get());
super(keyToClone.priv, keyToClone.pub.get(), keyToClone.pub.isCompressed());
this.parent = newParent;
this.childNumberPath = keyToClone.childNumberPath;
this.chainCode = keyToClone.chainCode;
Expand Down
Loading