diff --git a/core/src/main/java/org/bitcoinj/core/Address.java b/core/src/main/java/org/bitcoinj/core/Address.java index 56bd3e93..6e6a8870 100644 --- a/core/src/main/java/org/bitcoinj/core/Address.java +++ b/core/src/main/java/org/bitcoinj/core/Address.java @@ -18,18 +18,17 @@ package org.bitcoinj.core; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.Arrays; + +import javax.annotation.Nullable; import org.bitcoinj.params.Networks; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptPattern; -import javax.annotation.Nullable; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.base.Objects; /** *
A Bitcoin address looks like 1MsScoe2fTJoq4ZPdQgqyhgWeoNamYPevy and is derived from an elliptic curve public key @@ -48,7 +47,8 @@ public class Address extends VersionedChecksummedBytes { */ public static final int LENGTH = 20; - private transient NetworkParameters params; + /** True if P2SH, false if P2PKH. */ + public final boolean p2sh; /** * Private constructor. Use {@link #fromBase58(NetworkParameters, String)}, @@ -62,13 +62,10 @@ public class Address extends VersionedChecksummedBytes { * @param hash160 * 20-byte hash of pubkey or script */ - private Address(NetworkParameters params, int version, byte[] hash160) throws WrongNetworkException { - super(version, hash160); - checkNotNull(params); + private Address(NetworkParameters params, boolean p2sh, byte[] hash160) throws WrongNetworkException { + super(params, hash160); checkArgument(hash160.length == 20, "Addresses are 160-bit hashes, so you must provide 20 bytes"); - if (!isAcceptableVersion(params, version)) - throw new WrongNetworkException(version); - this.params = params; + this.p2sh = p2sh; } /** @@ -82,7 +79,7 @@ public class Address extends VersionedChecksummedBytes { * @return constructed address */ public static Address fromPubKeyHash(NetworkParameters params, byte[] hash160) { - return new Address(params, params.getAddressHeader(), hash160); + return new Address(params, false, hash160); } /** @@ -110,7 +107,7 @@ public class Address extends VersionedChecksummedBytes { */ public static Address fromP2SHHash(NetworkParameters params, byte[] hash160) { try { - return new Address(params, params.getP2SHHeader(), hash160); + return new Address(params, true, hash160); } catch (WrongNetworkException e) { throw new RuntimeException(e); // Cannot happen. } @@ -144,37 +141,35 @@ public class Address extends VersionedChecksummedBytes { * if the given address is valid but for a different chain (eg testnet vs mainnet) */ public static Address fromBase58(@Nullable NetworkParameters params, String base58) throws AddressFormatException { - return new Address(params, base58); + byte[] versionAndDataBytes = Base58.decodeChecked(base58); + int version = versionAndDataBytes[0] & 0xFF; + byte[] bytes = Arrays.copyOfRange(versionAndDataBytes, 1, versionAndDataBytes.length); + if (params == null) { + for (NetworkParameters p : Networks.get()) { + if (version == p.getAddressHeader()) + return new Address(p, false, bytes); + else if (version == p.getP2SHHeader()) + return new Address(p, true, bytes); + } + throw new AddressFormatException("No network found for " + base58); + } else { + if (version == params.getAddressHeader()) + return new Address(params, false, bytes); + else if (version == params.getP2SHHeader()) + return new Address(params, true, bytes); + throw new WrongNetworkException(version); + } } /** @deprecated use {@link #fromPubKeyHash(NetworkParameters, byte[])} */ @Deprecated public Address(NetworkParameters params, byte[] hash160) { - this(params, params.getAddressHeader(), hash160); + this(params, false, hash160); } - /** @deprecated Use {@link #fromBase58(NetworkParameters, String)} */ - @Deprecated - public Address(@Nullable NetworkParameters params, String address) throws AddressFormatException { - super(address); - if (params != null) { - if (!isAcceptableVersion(params, version)) { - throw new WrongNetworkException(version); - } - this.params = params; - } else { - NetworkParameters paramsFound = null; - for (NetworkParameters p : Networks.get()) { - if (isAcceptableVersion(p, version)) { - paramsFound = p; - break; - } - } - if (paramsFound == null) - throw new AddressFormatException("No network found for " + address); - - this.params = paramsFound; - } + @Override + protected int getVersion() { + return p2sh ? params.getP2SHHeader() : params.getAddressHeader(); } /** The (big endian) 20 byte hash that is the core of a Bitcoin address. */ @@ -187,20 +182,7 @@ public class Address extends VersionedChecksummedBytes { * See also https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki: Address Format for pay-to-script-hash */ public boolean isP2SHAddress() { - final NetworkParameters parameters = getParameters(); - return parameters != null && this.version == parameters.p2shHeader; - } - - /** - * Examines the version byte of the address and attempts to find a matching NetworkParameters. If you aren't sure - * which network the address is intended for (eg, it was provided by a user), you can use this to decide if it is - * compatible with the current wallet. You should be able to handle a null response from this method. Note that the - * parameters returned is not necessarily the same as the one the Address was created with. - * - * @return network the address is valid for - */ - public NetworkParameters getParameters() { - return params; + return p2sh; } /** @@ -219,31 +201,23 @@ public class Address extends VersionedChecksummedBytes { } } - /** - * Check if a given address version is valid given the NetworkParameters. - */ - private static boolean isAcceptableVersion(NetworkParameters params, int version) { - if (version == params.getAddressHeader()) + @Override + public boolean equals(Object o) { + if (this == o) return true; - if (version == params.getP2SHHeader()) - return true; - return false; + if (o == null || getClass() != o.getClass()) + return false; + Address other = (Address) o; + return super.equals(other) && this.p2sh == other.p2sh; + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), p2sh); } @Override public Address clone() throws CloneNotSupportedException { return (Address) super.clone(); } - - // Java serialization - - private void writeObject(ObjectOutputStream out) throws IOException { - out.defaultWriteObject(); - out.writeUTF(params.id); - } - - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - params = NetworkParameters.fromID(in.readUTF()); - } } diff --git a/core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java b/core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java index e71a72b1..25a67260 100644 --- a/core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java +++ b/core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java @@ -17,13 +17,14 @@ package org.bitcoinj.core; -import com.google.common.base.Objects; import com.google.common.base.Preconditions; import java.util.Arrays; import javax.annotation.Nullable; +import org.bitcoinj.params.Networks; + /** * Parses and generates private keys in the form used by the Bitcoin "dumpprivkey" command. This is the private key * bytes with a header byte and 4 checksum bytes at the end. If there are 33 private key bytes instead of 32, then @@ -42,13 +43,37 @@ public class DumpedPrivateKey extends VersionedChecksummedBytes { * @throws WrongNetworkException * if the given private key is valid but for a different chain (eg testnet vs mainnet) */ - public static DumpedPrivateKey fromBase58(@Nullable NetworkParameters params,String base58) throws AddressFormatException { - return new DumpedPrivateKey(params, base58); + public static DumpedPrivateKey fromBase58(@Nullable NetworkParameters params, String base58) + throws AddressFormatException { + byte[] versionAndDataBytes = Base58.decodeChecked(base58); + int version = versionAndDataBytes[0] & 0xFF; + byte[] bytes = Arrays.copyOfRange(versionAndDataBytes, 1, versionAndDataBytes.length); + if (params == null) { + for (NetworkParameters p : Networks.get()) + if (version == p.getDumpedPrivateKeyHeader()) + return new DumpedPrivateKey(p, bytes); + throw new AddressFormatException("No network found for " + base58); + } else { + if (version == params.getDumpedPrivateKeyHeader()) + return new DumpedPrivateKey(params, bytes); + throw new WrongNetworkException(version); + } + } + + private DumpedPrivateKey(NetworkParameters params, byte[] bytes) { + super(params, bytes); + if (bytes.length != 32 && bytes.length != 33) + throw new AddressFormatException("Wrong number of bytes for a private key, not 32 or 33"); } // Used by ECKey.getPrivateKeyEncoded() DumpedPrivateKey(NetworkParameters params, byte[] keyBytes, boolean compressed) { - super(params.getDumpedPrivateKeyHeader(), encode(keyBytes, compressed)); + this(params, encode(keyBytes, compressed)); + } + + @Override + protected int getVersion() { + return params.getDumpedPrivateKeyHeader(); } private static byte[] encode(byte[] keyBytes, boolean compressed) { @@ -64,17 +89,6 @@ public class DumpedPrivateKey extends VersionedChecksummedBytes { } } - /** @deprecated Use {@link #fromBase58(NetworkParameters, String)} */ - @Deprecated - public DumpedPrivateKey(@Nullable NetworkParameters params, String encoded) throws AddressFormatException { - super(encoded); - if (params != null && version != params.getDumpedPrivateKeyHeader()) - throw new WrongNetworkException(version); - if (bytes.length != 32 && bytes.length != 33) { - throw new AddressFormatException("Wrong number of bytes for a private key, not 32 or 33"); - } - } - /** * Returns an ECKey created from this encoded private key. */ @@ -88,17 +102,4 @@ public class DumpedPrivateKey extends VersionedChecksummedBytes { public boolean isPubKeyCompressed() { return bytes.length == 33 && bytes[32] == 1; } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - DumpedPrivateKey other = (DumpedPrivateKey) o; - return version == other.version && Arrays.equals(bytes, other.bytes); - } - - @Override - public int hashCode() { - return Objects.hashCode(version, Arrays.hashCode(bytes)); - } } diff --git a/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java b/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java index cbc325b9..39738273 100644 --- a/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java +++ b/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java @@ -1,5 +1,6 @@ /* * Copyright 2011 Google Inc. + * Copyright 2018 Andreas Schildbach * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,39 +17,44 @@ package org.bitcoinj.core; -import static com.google.common.base.Preconditions.checkArgument; - +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; +import java.lang.reflect.Field; import java.util.Arrays; import com.google.common.base.Objects; -import com.google.common.primitives.Ints; import com.google.common.primitives.UnsignedBytes; +import static com.google.common.base.Preconditions.checkNotNull; /** - *
In Bitcoin the following format is often used to represent some type of key:
- * - *[one version byte] [data bytes] [4 checksum bytes]- * - *
and the result is then Base58 encoded. This format is used for addresses, and private keys exported using the - * dumpprivkey command.
+ *+ * The following format is often used to represent some type of data (e.g. key or hash of key): + *
+ * + *+ * [prefix] [data bytes] [checksum] + *+ *
+ * and the result is then encoded with some variant of base. This format is most commonly used for addresses and private + * keys exported using Bitcoin Core's dumpprivkey command. + *
*/ -public class VersionedChecksummedBytes implements Serializable, Cloneable, ComparableVersionedChecksummedBytes
* and allows subclasses to throw CloneNotSupportedException
even though it
* is never thrown by this implementation.
@@ -90,23 +96,30 @@ public class VersionedChecksummedBytes implements Serializable, Cloneable, Compa
}
/**
- * {@inheritDoc}
- *
* This implementation uses an optimized Google Guava method to compare bytes
.
*/
@Override
public int compareTo(VersionedChecksummedBytes o) {
- int result = Ints.compare(this.version, o.version);
+ int result = this.params.getId().compareTo(o.params.getId());
return result != 0 ? result : UnsignedBytes.lexicographicalComparator().compare(this.bytes, o.bytes);
}
- /**
- * Returns the "version" or "header" byte: the first byte of the data. This is used to disambiguate what the
- * contents apply to, for example, which network the key or address is valid on.
- *
- * @return A positive number between 0 and 255.
- */
- public int getVersion() {
- return version;
+ // Java serialization
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.defaultWriteObject();
+ out.writeUTF(params.getId());
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ try {
+ Field paramsField = VersionedChecksummedBytes.class.getDeclaredField("params");
+ paramsField.setAccessible(true);
+ paramsField.set(this, checkNotNull(NetworkParameters.fromID(in.readUTF())));
+ paramsField.setAccessible(false);
+ } catch (NoSuchFieldException | IllegalAccessException x) {
+ throw new RuntimeException(x);
+ }
}
}
diff --git a/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java b/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java
index 7cba10bb..b4dc55b3 100644
--- a/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java
+++ b/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java
@@ -18,22 +18,17 @@ package org.bitcoinj.crypto;
import org.bitcoinj.core.*;
import com.google.common.base.Charsets;
-import com.google.common.base.Objects;
import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.text.Normalizer;
import java.util.Arrays;
-import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
/**
@@ -41,8 +36,6 @@ import static com.google.common.base.Preconditions.checkState;
* passphrase-protected private keys. Currently, only decryption is supported.
*/
public class BIP38PrivateKey extends VersionedChecksummedBytes {
-
- public transient NetworkParameters params;
public final boolean ecMultiply;
public final boolean compressed;
public final boolean hasLotAndSequence;
@@ -62,20 +55,16 @@ public class BIP38PrivateKey extends VersionedChecksummedBytes {
* if the given base58 doesn't parse or the checksum is invalid
*/
public static BIP38PrivateKey fromBase58(NetworkParameters params, String base58) throws AddressFormatException {
- return new BIP38PrivateKey(params, base58);
- }
+ byte[] versionAndDataBytes = Base58.decodeChecked(base58);
+ int version = versionAndDataBytes[0] & 0xFF;
+ byte[] bytes = Arrays.copyOfRange(versionAndDataBytes, 1, versionAndDataBytes.length);
- /** @deprecated Use {@link #fromBase58(NetworkParameters, String)} */
- @Deprecated
- public BIP38PrivateKey(NetworkParameters params, String encoded) throws AddressFormatException {
- super(encoded);
- this.params = checkNotNull(params);
if (version != 0x01)
throw new AddressFormatException("Mismatched version number: " + version);
if (bytes.length != 38)
throw new AddressFormatException("Wrong number of bytes, excluding version byte: " + bytes.length);
- hasLotAndSequence = (bytes[1] & 0x04) != 0; // bit 2
- compressed = (bytes[1] & 0x20) != 0; // bit 5
+ boolean hasLotAndSequence = (bytes[1] & 0x04) != 0; // bit 2
+ boolean compressed = (bytes[1] & 0x20) != 0; // bit 5
if ((bytes[1] & 0x01) != 0) // bit 0
throw new AddressFormatException("Bit 0x01 reserved for future use.");
if ((bytes[1] & 0x02) != 0) // bit 1
@@ -85,6 +74,7 @@ public class BIP38PrivateKey extends VersionedChecksummedBytes {
if ((bytes[1] & 0x10) != 0) // bit 4
throw new AddressFormatException("Bit 0x10 reserved for future use.");
final int byte0 = bytes[0] & 0xff;
+ final boolean ecMultiply;
if (byte0 == 0x42) {
// Non-EC-multiplied key
if ((bytes[1] & 0xc0) != 0xc0) // bits 6+7
@@ -100,8 +90,24 @@ public class BIP38PrivateKey extends VersionedChecksummedBytes {
} else {
throw new AddressFormatException("Second byte must by 0x42 or 0x43.");
}
- addressHash = Arrays.copyOfRange(bytes, 2, 6);
- content = Arrays.copyOfRange(bytes, 6, 38);
+ byte[] addressHash = Arrays.copyOfRange(bytes, 2, 6);
+ byte[] content = Arrays.copyOfRange(bytes, 6, 38);
+ return new BIP38PrivateKey(params, bytes, ecMultiply, compressed, hasLotAndSequence, addressHash, content);
+ }
+
+ private BIP38PrivateKey(NetworkParameters params, byte[] bytes, boolean ecMultiply, boolean compressed,
+ boolean hasLotAndSequence, byte[] addressHash, byte[] content) throws AddressFormatException {
+ super(params, bytes);
+ this.ecMultiply = ecMultiply;
+ this.compressed = compressed;
+ this.hasLotAndSequence = hasLotAndSequence;
+ this.addressHash = addressHash;
+ this.content = content;
+ }
+
+ @Override
+ protected int getVersion() {
+ return 1;
}
public ECKey decrypt(String passphrase) throws BadPassphraseException {
@@ -180,29 +186,4 @@ public class BIP38PrivateKey extends VersionedChecksummedBytes {
throw new RuntimeException(x);
}
}
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- BIP38PrivateKey other = (BIP38PrivateKey) o;
- return super.equals(other) && Objects.equal(this.params, other.params);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(super.hashCode(), params);
- }
-
- // Java serialization
-
- private void writeObject(ObjectOutputStream out) throws IOException {
- out.defaultWriteObject();
- out.writeUTF(params.getId());
- }
-
- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
- in.defaultReadObject();
- params = checkNotNull(NetworkParameters.fromID(in.readUTF()));
- }
}
diff --git a/core/src/test/java/org/bitcoinj/core/AddressTest.java b/core/src/test/java/org/bitcoinj/core/AddressTest.java
index f3652f7f..8bbe0b51 100644
--- a/core/src/test/java/org/bitcoinj/core/AddressTest.java
+++ b/core/src/test/java/org/bitcoinj/core/AddressTest.java
@@ -149,11 +149,11 @@ public class AddressTest {
@Test
public void p2shAddress() throws Exception {
// Test that we can construct P2SH addresses
- Address mainNetP2SHAddress = Address.fromBase58(MAINNET, "35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU");
- assertEquals(mainNetP2SHAddress.version, MAINNET.p2shHeader);
+ Address mainNetP2SHAddress = Address.fromBase58(MainNetParams.get(), "35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU");
+ assertEquals(mainNetP2SHAddress.getVersion(), MAINNET.p2shHeader);
assertTrue(mainNetP2SHAddress.isP2SHAddress());
- Address testNetP2SHAddress = Address.fromBase58(TESTNET, "2MuVSxtfivPKJe93EC1Tb9UhJtGhsoWEHCe");
- assertEquals(testNetP2SHAddress.version, TESTNET.p2shHeader);
+ Address testNetP2SHAddress = Address.fromBase58(TestNet3Params.get(), "2MuVSxtfivPKJe93EC1Tb9UhJtGhsoWEHCe");
+ assertEquals(testNetP2SHAddress.getVersion(), TESTNET.p2shHeader);
assertTrue(testNetP2SHAddress.isP2SHAddress());
// Test that we can determine what network a P2SH address belongs to
diff --git a/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java b/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java
index d3b9fb9b..4159a038 100644
--- a/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java
+++ b/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java
@@ -29,19 +29,30 @@ public class VersionedChecksummedBytesTest {
private static final NetworkParameters TESTNET = TestNet3Params.get();
private static final NetworkParameters MAINNET = MainNetParams.get();
+ private static class VersionedChecksummedBytesToTest extends VersionedChecksummedBytes {
+ public VersionedChecksummedBytesToTest(NetworkParameters params, byte[] bytes) {
+ super(params, bytes);
+ }
+
+ @Override
+ protected int getVersion() {
+ return params.getAddressHeader();
+ }
+ }
+
@Test
public void stringification() throws Exception {
// Test a testnet address.
- VersionedChecksummedBytes a = new VersionedChecksummedBytes(TESTNET.getAddressHeader(), HEX.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc"));
+ VersionedChecksummedBytes a = new VersionedChecksummedBytesToTest(TESTNET, HEX.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc"));
assertEquals("n4eA2nbYqErp7H6jebchxAN59DmNpksexv", a.toString());
- VersionedChecksummedBytes b = new VersionedChecksummedBytes(MAINNET.getAddressHeader(), HEX.decode("4a22c3c4cbb31e4d03b15550636762bda0baf85a"));
+ VersionedChecksummedBytes b = new VersionedChecksummedBytesToTest(MAINNET, HEX.decode("4a22c3c4cbb31e4d03b15550636762bda0baf85a"));
assertEquals("17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL", b.toString());
}
@Test
public void cloning() throws Exception {
- VersionedChecksummedBytes a = new VersionedChecksummedBytes(TESTNET.getAddressHeader(), HEX.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc"));
+ VersionedChecksummedBytes a = new VersionedChecksummedBytesToTest(TESTNET, HEX.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc"));
VersionedChecksummedBytes b = a.clone();
assertEquals(a, b);
@@ -50,7 +61,7 @@ public class VersionedChecksummedBytesTest {
@Test
public void comparisonCloneEqualTo() throws Exception {
- VersionedChecksummedBytes a = new VersionedChecksummedBytes(TESTNET.getAddressHeader(), HEX.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc"));
+ VersionedChecksummedBytes a = new VersionedChecksummedBytesToTest(TESTNET, HEX.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc"));
VersionedChecksummedBytes b = a.clone();
assertTrue(a.compareTo(b) == 0);