Address: convert to interface

* Migrate protected members to subclasses
* Remove constructors
* Migrate hashCode and equals to subclasses
This commit is contained in:
Sean Gilligan 2023-03-03 17:49:00 -08:00 committed by Andreas Schildbach
parent 6c84ffec85
commit f9be0b2a3d
4 changed files with 69 additions and 90 deletions

View file

@ -21,46 +21,15 @@ import org.bitcoinj.crypto.ECKey;
import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.NetworkParameters;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/** /**
* Base class for addresses, e.g. native segwit addresses ({@link SegwitAddress}) or legacy addresses ({@link LegacyAddress}). * Interface for addresses, e.g. native segwit addresses ({@link SegwitAddress}) or legacy addresses ({@link LegacyAddress}).
* <p> * <p>
* Use an implementation of {@link AddressParser#parseAddress(String, Network)} to conveniently construct any kind of address from its textual * Use an implementation of {@link AddressParser#parseAddress(String, Network)} to conveniently construct any kind of address from its textual
* form. * form.
*/ */
public abstract class Address implements Comparable<Address> { public interface Address extends Comparable<Address> {
protected final Network network;
protected final byte[] bytes;
/**
* Construct an address from its binary form.
*
* @param params the network this address is valid for
* @param bytes the binary address data
* @deprecated Use {@link Address#Address(Network, byte[])}
*/
@Deprecated
protected Address(NetworkParameters params, byte[] bytes) {
this.network = checkNotNull(params).network();
this.bytes = checkNotNull(bytes);
}
/**
* Construct an address from its binary form.
*
* @param network the network this address is valid for
* @param bytes the binary address data
*/
protected Address(Network network, byte[] bytes) {
this.network = checkNotNull(network);
this.bytes = checkNotNull(bytes);
}
/** /**
* Construct an address from its textual form. * Construct an address from its textual form.
* *
@ -76,7 +45,7 @@ public abstract class Address implements Comparable<Address> {
* @deprecated Use {@link org.bitcoinj.wallet.Wallet#parseAddress(String)} or {@link AddressParser#parseAddress(String, Network)} * @deprecated Use {@link org.bitcoinj.wallet.Wallet#parseAddress(String)} or {@link AddressParser#parseAddress(String, Network)}
*/ */
@Deprecated @Deprecated
public static Address fromString(@Nullable NetworkParameters params, String str) static Address fromString(@Nullable NetworkParameters params, String str)
throws AddressFormatException { throws AddressFormatException {
AddressParser addressParser = DefaultAddressParser.fromNetworks(); AddressParser addressParser = DefaultAddressParser.fromNetworks();
return (params != null) return (params != null)
@ -97,7 +66,7 @@ public abstract class Address implements Comparable<Address> {
* @deprecated Use {@link ECKey#toAddress(ScriptType, Network)} * @deprecated Use {@link ECKey#toAddress(ScriptType, Network)}
*/ */
@Deprecated @Deprecated
public static Address fromKey(final NetworkParameters params, final ECKey key, final ScriptType outputScriptType) { static Address fromKey(final NetworkParameters params, final ECKey key, final ScriptType outputScriptType) {
return key.toAddress(outputScriptType, params.network()); return key.toAddress(outputScriptType, params.network());
} }
@ -106,21 +75,8 @@ public abstract class Address implements Comparable<Address> {
* @deprecated Use {@link #network()} * @deprecated Use {@link #network()}
*/ */
@Deprecated @Deprecated
public final NetworkParameters getParameters() { default NetworkParameters getParameters() {
return NetworkParameters.of(network); return NetworkParameters.of(network());
}
@Override
public int hashCode() {
return Objects.hash(network, Arrays.hashCode(bytes));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address other = (Address) o;
return this.network == other.network && Arrays.equals(this.bytes, other.bytes);
} }
/** /**
@ -128,14 +84,14 @@ public abstract class Address implements Comparable<Address> {
* *
* @return hash that is encoded in the address * @return hash that is encoded in the address
*/ */
public abstract byte[] getHash(); byte[] getHash();
/** /**
* Get the type of output script that will be used for sending to the address. * Get the type of output script that will be used for sending to the address.
* *
* @return type of output script * @return type of output script
*/ */
public abstract ScriptType getOutputScriptType(); ScriptType getOutputScriptType();
/** /**
* Comparison field order for addresses is: * Comparison field order for addresses is:
@ -152,26 +108,26 @@ public abstract class Address implements Comparable<Address> {
* @return comparison result * @return comparison result
*/ */
@Override @Override
abstract public int compareTo(Address o); int compareTo(Address o);
/** /**
* Get the network this address works on. Use of {@link BitcoinNetwork} is preferred to use of {@link NetworkParameters} * Get the network this address works on. Use of {@link BitcoinNetwork} is preferred to use of {@link NetworkParameters}
* when you need to know what network an address is for. * when you need to know what network an address is for.
* @return the Network. * @return the Network.
*/ */
public Network network() { Network network();
return network;
}
/** /**
* Comparator for the first two comparison fields in {@code Address} comparisons, see {@link Address#compareTo(Address)}. * Comparator for the first two comparison fields in {@code Address} comparisons, see {@link Address#compareTo(Address)}.
* Used by {@link LegacyAddress#compareTo(Address)} and {@link SegwitAddress#compareTo(Address)}. * Used by {@link LegacyAddress#compareTo(Address)} and {@link SegwitAddress#compareTo(Address)}.
* For use by implementing classes only.
*/ */
protected static final Comparator<Address> PARTIAL_ADDRESS_COMPARATOR = Comparator Comparator<Address> PARTIAL_ADDRESS_COMPARATOR = Comparator
.comparing((Address a) -> a.network.id()) // First compare network .comparing((Address a) -> a.network().id()) // First compare network
.thenComparing(Address::compareTypes); // Then compare address type (subclass) .thenComparing(Address::compareTypes); // Then compare address type (subclass)
private static int compareTypes(Address a, Address b) { /* private */
static int compareTypes(Address a, Address b) {
if (a instanceof LegacyAddress && b instanceof SegwitAddress) { if (a instanceof LegacyAddress && b instanceof SegwitAddress) {
return -1; // Legacy addresses (starting with 1 or 3) come before Segwit addresses. return -1; // Legacy addresses (starting with 1 or 3) come before Segwit addresses.
} else if (a instanceof SegwitAddress && b instanceof LegacyAddress) { } else if (a instanceof SegwitAddress && b instanceof LegacyAddress) {

View file

@ -29,6 +29,8 @@ import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects; import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/** /**
* <p>A Bitcoin address looks like 1MsScoe2fTJoq4ZPdQgqyhgWeoNamYPevy and is derived from an elliptic curve public key * <p>A Bitcoin address looks like 1MsScoe2fTJoq4ZPdQgqyhgWeoNamYPevy and is derived from an elliptic curve public key
* plus a set of network parameters. Not to be confused with a {@link org.bitcoinj.core.PeerAddress} * plus a set of network parameters. Not to be confused with a {@link org.bitcoinj.core.PeerAddress}
@ -40,12 +42,14 @@ import java.util.Objects;
* should be interpreted. Whilst almost all addresses today are hashes of public keys, another (currently unsupported * should be interpreted. Whilst almost all addresses today are hashes of public keys, another (currently unsupported
* type) can contain a hash of a script instead.</p> * type) can contain a hash of a script instead.</p>
*/ */
public class LegacyAddress extends Address { public class LegacyAddress implements Address {
/** /**
* An address is a RIPEMD160 hash of a public key, therefore is always 160 bits or 20 bytes. * An address is a RIPEMD160 hash of a public key, therefore is always 160 bits or 20 bytes.
*/ */
public static final int LENGTH = 20; public static final int LENGTH = 20;
protected final Network network;
protected final byte[] bytes;
/** True if P2SH, false if P2PKH. */ /** True if P2SH, false if P2PKH. */
public final boolean p2sh; public final boolean p2sh;
@ -62,7 +66,8 @@ public class LegacyAddress extends Address {
* 20-byte hash of pubkey or script * 20-byte hash of pubkey or script
*/ */
private LegacyAddress(Network network, boolean p2sh, byte[] hash160) throws AddressFormatException { private LegacyAddress(Network network, boolean p2sh, byte[] hash160) throws AddressFormatException {
super(normalizeNetwork(network), hash160); this.network = normalizeNetwork(checkNotNull(network));
this.bytes = checkNotNull(hash160);
if (hash160.length != 20) if (hash160.length != 20)
throw new AddressFormatException.InvalidDataLength( throw new AddressFormatException.InvalidDataLength(
"Legacy addresses are 20 byte (160 bit) hashes, but got: " + hash160.length); "Legacy addresses are 20 byte (160 bit) hashes, but got: " + hash160.length);
@ -194,6 +199,16 @@ public class LegacyAddress extends Address {
throw new AddressFormatException.WrongNetwork(version); throw new AddressFormatException.WrongNetwork(version);
} }
/**
* Get the network this address works on. Use of {@link BitcoinNetwork} is preferred to use of {@link NetworkParameters}
* when you need to know what network an address is for.
* @return the Network.
*/
@Override
public Network network() {
return network;
}
/** /**
* Get the version header of an address. This is the first byte of a base58 encoded address. * Get the version header of an address. This is the first byte of a base58 encoded address.
* *
@ -250,12 +265,12 @@ public class LegacyAddress extends Address {
if (o == null || getClass() != o.getClass()) if (o == null || getClass() != o.getClass())
return false; return false;
LegacyAddress other = (LegacyAddress) o; LegacyAddress other = (LegacyAddress) o;
return super.equals(other) && this.p2sh == other.p2sh; return this.network == other.network && Arrays.equals(this.bytes, other.bytes) && this.p2sh == other.p2sh;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(super.hashCode(), p2sh); return Objects.hash(network, Arrays.hashCode(bytes), p2sh);
} }
@Override @Override
@ -263,15 +278,10 @@ public class LegacyAddress extends Address {
return toBase58(); return toBase58();
} }
@Override
public LegacyAddress clone() throws CloneNotSupportedException {
return (LegacyAddress) super.clone();
}
// Comparator for LegacyAddress, left argument must be LegacyAddress, right argument can be any Address // Comparator for LegacyAddress, left argument must be LegacyAddress, right argument can be any Address
private static final Comparator<Address> LEGACY_ADDRESS_COMPARATOR = Address.PARTIAL_ADDRESS_COMPARATOR private static final Comparator<Address> LEGACY_ADDRESS_COMPARATOR = Address.PARTIAL_ADDRESS_COMPARATOR
.thenComparingInt(a -> ((LegacyAddress) a).getVersion()) // Then compare Legacy address version byte .thenComparingInt(a -> ((LegacyAddress) a).getVersion()) // Then compare Legacy address version byte
.thenComparing(a -> a.bytes, ByteUtils.arrayUnsignedComparator()); // Then compare Legacy bytes .thenComparing(a -> ((LegacyAddress) a).bytes, ByteUtils.arrayUnsignedComparator()); // Then compare Legacy bytes
/** /**
* {@inheritDoc} * {@inheritDoc}

View file

@ -24,12 +24,14 @@ import org.bitcoinj.core.NetworkParameters;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull;
import static org.bitcoinj.base.BitcoinNetwork.*; import static org.bitcoinj.base.BitcoinNetwork.*;
/** /**
@ -49,7 +51,7 @@ import static org.bitcoinj.base.BitcoinNetwork.*;
* {@link #fromHash(org.bitcoinj.base.Network, byte[])} or {@link ECKey#toAddress(ScriptType, Network)} * {@link #fromHash(org.bitcoinj.base.Network, byte[])} or {@link ECKey#toAddress(ScriptType, Network)}
* to construct a native segwit address.</p> * to construct a native segwit address.</p>
*/ */
public class SegwitAddress extends Address { public class SegwitAddress implements Address {
public static final int WITNESS_PROGRAM_LENGTH_PKH = 20; public static final int WITNESS_PROGRAM_LENGTH_PKH = 20;
public static final int WITNESS_PROGRAM_LENGTH_SH = 32; public static final int WITNESS_PROGRAM_LENGTH_SH = 32;
public static final int WITNESS_PROGRAM_LENGTH_TR = 32; public static final int WITNESS_PROGRAM_LENGTH_TR = 32;
@ -119,6 +121,9 @@ public class SegwitAddress extends Address {
} }
} }
protected final Network network;
protected final byte[] bytes;
/** /**
* Private constructor. Use {@link #fromBech32(Network, String)}, * Private constructor. Use {@link #fromBech32(Network, String)},
* {@link #fromHash(Network, byte[])} or {@link ECKey#toAddress(ScriptType, Network)}. * {@link #fromHash(Network, byte[])} or {@link ECKey#toAddress(ScriptType, Network)}.
@ -169,7 +174,8 @@ public class SegwitAddress extends Address {
* if any of the sanity checks fail * if any of the sanity checks fail
*/ */
private SegwitAddress(Network network, byte[] data) throws AddressFormatException { private SegwitAddress(Network network, byte[] data) throws AddressFormatException {
super(normalizeNetwork(network), data); this.network = normalizeNetwork(checkNotNull(network));
this.bytes = checkNotNull(data);
if (data.length < 1) if (data.length < 1)
throw new AddressFormatException.InvalidDataLength("Zero data found"); throw new AddressFormatException.InvalidDataLength("Zero data found");
final int witnessVersion = getWitnessVersion(); final int witnessVersion = getWitnessVersion();
@ -380,6 +386,29 @@ public class SegwitAddress extends Address {
return (SegwitAddress) key.toAddress(ScriptType.P2WPKH, params.network()); return (SegwitAddress) key.toAddress(ScriptType.P2WPKH, params.network());
} }
/**
* Get the network this address works on. Use of {@link BitcoinNetwork} is preferred to use of {@link NetworkParameters}
* when you need to know what network an address is for.
* @return the Network.
*/
@Override
public Network network() {
return network;
}
@Override
public int hashCode() {
return Objects.hash(network, Arrays.hashCode(bytes));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SegwitAddress other = (SegwitAddress) o;
return this.network == other.network && Arrays.equals(this.bytes, other.bytes);
}
/** /**
* Returns the textual form of the address. * Returns the textual form of the address.
* *
@ -426,7 +455,7 @@ public class SegwitAddress extends Address {
// Comparator for SegwitAddress, left argument must be SegwitAddress, right argument can be any Address // Comparator for SegwitAddress, left argument must be SegwitAddress, right argument can be any Address
private static final Comparator<Address> SEGWIT_ADDRESS_COMPARATOR = Address.PARTIAL_ADDRESS_COMPARATOR private static final Comparator<Address> SEGWIT_ADDRESS_COMPARATOR = Address.PARTIAL_ADDRESS_COMPARATOR
.thenComparing(a -> a.bytes, ByteUtils.arrayUnsignedComparator()); // Then compare Segwit bytes .thenComparing(a -> ((SegwitAddress) a).bytes, ByteUtils.arrayUnsignedComparator()); // Then compare Segwit bytes
/** /**
* {@inheritDoc} * {@inheritDoc}

View file

@ -16,22 +16,6 @@
package org.bitcoinj.base; package org.bitcoinj.base;
import nl.jqno.equalsverifier.EqualsVerifier; // TODO: Maybe add some tests here. Address has minimal functionality now, however -- see LegacyAddress and SegwitAddress
import nl.jqno.equalsverifier.Warning;
import org.bitcoinj.base.Address;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.TestNet3Params;
import org.junit.Test;
public class AddressTest { public class AddressTest {
@Test
public void equalsContract() {
EqualsVerifier.forClass(Address.class)
.withPrefabValues(NetworkParameters.class, MainNetParams.get(), TestNet3Params.get())
.suppress(Warning.NULL_FIELDS)
.suppress(Warning.TRANSIENT_FIELDS)
.usingGetClass()
.verify();
}
} }