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 javax.annotation.Nullable;
import java.util.Arrays;
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>
* Use an implementation of {@link AddressParser#parseAddress(String, Network)} to conveniently construct any kind of address from its textual
* form.
*/
public abstract class Address implements 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);
}
public interface Address extends Comparable<Address> {
/**
* 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
public static Address fromString(@Nullable NetworkParameters params, String str)
static Address fromString(@Nullable NetworkParameters params, String str)
throws AddressFormatException {
AddressParser addressParser = DefaultAddressParser.fromNetworks();
return (params != null)
@ -97,7 +66,7 @@ public abstract class Address implements Comparable<Address> {
* @deprecated Use {@link ECKey#toAddress(ScriptType, Network)}
*/
@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());
}
@ -106,21 +75,8 @@ public abstract class Address implements Comparable<Address> {
* @deprecated Use {@link #network()}
*/
@Deprecated
public final NetworkParameters getParameters() {
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);
default NetworkParameters getParameters() {
return NetworkParameters.of(network());
}
/**
@ -128,14 +84,14 @@ public abstract class Address implements Comparable<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.
*
* @return type of output script
*/
public abstract ScriptType getOutputScriptType();
ScriptType getOutputScriptType();
/**
* Comparison field order for addresses is:
@ -152,26 +108,26 @@ public abstract class Address implements Comparable<Address> {
* @return comparison result
*/
@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}
* when you need to know what network an address is for.
* @return the Network.
*/
public Network network() {
return network;
}
Network network();
/**
* 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)}.
* For use by implementing classes only.
*/
protected static final Comparator<Address> PARTIAL_ADDRESS_COMPARATOR = Comparator
.comparing((Address a) -> a.network.id()) // First compare network
Comparator<Address> PARTIAL_ADDRESS_COMPARATOR = Comparator
.comparing((Address a) -> a.network().id()) // First compare network
.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) {
return -1; // Legacy addresses (starting with 1 or 3) come before Segwit addresses.
} 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.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
* 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
* 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.
*/
public static final int LENGTH = 20;
protected final Network network;
protected final byte[] bytes;
/** True if P2SH, false if P2PKH. */
public final boolean p2sh;
@ -62,7 +66,8 @@ public class LegacyAddress extends Address {
* 20-byte hash of pubkey or script
*/
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)
throw new AddressFormatException.InvalidDataLength(
"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);
}
/**
* 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.
*
@ -250,12 +265,12 @@ public class LegacyAddress extends Address {
if (o == null || getClass() != o.getClass())
return false;
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
public int hashCode() {
return Objects.hash(super.hashCode(), p2sh);
return Objects.hash(network, Arrays.hashCode(bytes), p2sh);
}
@Override
@ -263,15 +278,10 @@ public class LegacyAddress extends Address {
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
private static final Comparator<Address> LEGACY_ADDRESS_COMPARATOR = Address.PARTIAL_ADDRESS_COMPARATOR
.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}

View file

@ -24,12 +24,14 @@ import org.bitcoinj.core.NetworkParameters;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Optional;
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.*;
/**
@ -49,7 +51,7 @@ import static org.bitcoinj.base.BitcoinNetwork.*;
* {@link #fromHash(org.bitcoinj.base.Network, byte[])} or {@link ECKey#toAddress(ScriptType, Network)}
* 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_SH = 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)},
* {@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
*/
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)
throw new AddressFormatException.InvalidDataLength("Zero data found");
final int witnessVersion = getWitnessVersion();
@ -380,6 +386,29 @@ public class SegwitAddress extends Address {
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.
*
@ -426,7 +455,7 @@ public class SegwitAddress extends 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
.thenComparing(a -> a.bytes, ByteUtils.arrayUnsignedComparator()); // Then compare Segwit bytes
.thenComparing(a -> ((SegwitAddress) a).bytes, ByteUtils.arrayUnsignedComparator()); // Then compare Segwit bytes
/**
* {@inheritDoc}

View file

@ -16,22 +16,6 @@
package org.bitcoinj.base;
import nl.jqno.equalsverifier.EqualsVerifier;
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;
// TODO: Maybe add some tests here. Address has minimal functionality now, however -- see LegacyAddress and SegwitAddress
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();
}
}