Skip to content

Commit

Permalink
VersionedChecksummedBytes: Store network params rather than version, …
Browse files Browse the repository at this point in the history
…in preparation for native segwit addresses.
  • Loading branch information
Andreas Schildbach committed Feb 27, 2018
1 parent 1d15d95 commit afcdc81
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 190 deletions.
122 changes: 48 additions & 74 deletions core/src/main/java/org/bitcoinj/core/Address.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
* <p>A Bitcoin address looks like 1MsScoe2fTJoq4ZPdQgqyhgWeoNamYPevy and is derived from an elliptic curve public key
Expand All @@ -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)},
Expand All @@ -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;
}

/**
Expand All @@ -82,7 +79,7 @@ private Address(NetworkParameters params, int version, byte[] hash160) throws Wr
* @return constructed address
*/
public static Address fromPubKeyHash(NetworkParameters params, byte[] hash160) {
return new Address(params, params.getAddressHeader(), hash160);
return new Address(params, false, hash160);
}

/**
Expand Down Expand Up @@ -110,7 +107,7 @@ public static Address fromKey(NetworkParameters params, ECKey key) {
*/
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.
}
Expand Down Expand Up @@ -144,37 +141,35 @@ public static Address fromP2SHScript(NetworkParameters params, Script scriptPubK
* 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. */
Expand All @@ -187,20 +182,7 @@ public byte[] getHash160() {
* 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;
}

/**
Expand All @@ -219,31 +201,23 @@ public static NetworkParameters getParametersFromAddress(String address) throws
}
}

/**
* Check if a given address version is valid given the NetworkParameters.
*/
private static boolean isAcceptableVersion(NetworkParameters params, int version) {
if (version == params.getAddressHeader())
return true;
if (version == params.getP2SHHeader())
@Override
public boolean equals(Object o) {
if (this == o)
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 Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
public int hashCode() {
return Objects.hashCode(super.hashCode(), p2sh);
}

// 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());
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
57 changes: 29 additions & 28 deletions core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -64,17 +89,6 @@ private static byte[] encode(byte[] keyBytes, boolean compressed) {
}
}

/** @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.
*/
Expand All @@ -88,17 +102,4 @@ public ECKey getKey() {
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));
}
}
Loading

0 comments on commit afcdc81

Please sign in to comment.