BitcoinNetwork: add address validation methods

There are use cases where an already parsed address needs to be validated
for a specific network. In one use case the address came from an external
source and needs validation with an error message displayed: that's what
`isValidAddress(Address)` is for. In the other case we can treat the invalid
address as a fatal error. That's `checkAddress(Address)`.
This commit is contained in:
Sean Gilligan 2024-04-28 12:41:21 -07:00 committed by Andreas Schildbach
parent f1a5ebe71e
commit 53a6b3e150
2 changed files with 103 additions and 0 deletions

View file

@ -182,6 +182,57 @@ public enum BitcoinNetwork implements Network {
}
}
/**
* Check if an address is valid on this network.
* This is meant to be used as a precondition for a method or function that expects a valid address. If
* you are validating addresses provided externally, you probably want to use
* {@link #isValidAddress(Address)} to handle errors more gracefully. This method uses {@link #isValidAddress(Address)}
* internally which properly accounts for address normalization.
* @param address Address to validate
* @return The unmodified address if valid on this network
* @throws IllegalArgumentException if address not valid on this network
*/
public Address checkAddress(Address address) throws IllegalArgumentException {
if (!isValidAddress(address)) {
throw new IllegalArgumentException(String.format("Address %s not valid on network %s", address, this));
}
return address;
}
/**
* Is address valid for this network. Because we normalize the {@code network()} value in the {@link Address}
* type (see the JavaDoc for {@link Address#network()}) this method should be used in preference to simply
* verifying that {@code address.network()} returns the desired network type.
* @param address Address to validate
* @return {@code true} if valid on this network, {@code false} otherwise
*/
public boolean isValidAddress(Address address) {
boolean valid;
switch (this) {
case MAINNET:
valid = address.network() == MAINNET;
break;
case TESTNET:
case SIGNET:
// SIGNET uses the same addresses as TESTNET
valid = address.network() == TESTNET;
break;
case REGTEST:
if (address instanceof LegacyAddress) {
// For Legacy addresses, REGTEST uses TESTNET addresses
valid = ((LegacyAddress) address).network == TESTNET;
} else {
// On segwit, REGTEST has its own address type
valid = address.network() == REGTEST;
}
break;
default:
valid = false;
break;
}
return valid;
}
/**
* Find the {@code BitcoinNetwork} from a name string, e.g. "mainnet", "testnet" or "signet".
* A number of common alternate names are allowed too, e.g. "main" or "prod".

View file

@ -18,8 +18,13 @@ package org.bitcoinj.base;
import org.junit.Test;
import static org.bitcoinj.base.BitcoinNetwork.MAINNET;
import static org.bitcoinj.base.BitcoinNetwork.REGTEST;
import static org.bitcoinj.base.BitcoinNetwork.SIGNET;
import static org.bitcoinj.base.BitcoinNetwork.TESTNET;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class BitcoinNetworkTest {
@Test
@ -79,4 +84,51 @@ public class BitcoinNetworkTest {
public void fromIdString_notExisting() {
assertFalse(BitcoinNetwork.fromIdString("a.b.c").isPresent());
}
@Test
public void testLegacyAddressValidity() {
LegacyAddress m = LegacyAddress.fromBase58("17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL", MAINNET);
LegacyAddress t = LegacyAddress.fromBase58("n4eA2nbYqErp7H6jebchxAN59DmNpksexv", TESTNET);
assertTrue(MAINNET.isValidAddress(m));
assertTrue(TESTNET.isValidAddress(t));
assertTrue(SIGNET.isValidAddress(t));
assertTrue(REGTEST.isValidAddress(t));
assertFalse(MAINNET.isValidAddress(t));
assertFalse(TESTNET.isValidAddress(m));
assertFalse(SIGNET.isValidAddress(m));
assertFalse(REGTEST.isValidAddress(m));
}
@Test
public void testSegwitAddressValidity() {
SegwitAddress m = SegwitAddress.fromBech32("bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", MAINNET);
SegwitAddress t = SegwitAddress.fromBech32("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", TESTNET);
SegwitAddress rt = SegwitAddress.fromBech32("bcrt1qspfueag7fvty7m8htuzare3xs898zvh30fttu2", REGTEST);
assertTrue(MAINNET.isValidAddress(m));
assertTrue(TESTNET.isValidAddress(t));
assertTrue(SIGNET.isValidAddress(t));
assertTrue(REGTEST.isValidAddress(rt));
assertFalse(MAINNET.isValidAddress(t));
assertFalse(MAINNET.isValidAddress(rt));
assertFalse(TESTNET.isValidAddress(m));
assertFalse(TESTNET.isValidAddress(rt));
assertFalse(SIGNET.isValidAddress(m));
assertFalse(SIGNET.isValidAddress(rt));
assertFalse(REGTEST.isValidAddress(m));
assertFalse(REGTEST.isValidAddress(t));
}
@Test(expected = IllegalArgumentException.class)
public void testLegacyAddressCheckThrow() {
MAINNET.checkAddress(LegacyAddress.fromBase58("n4eA2nbYqErp7H6jebchxAN59DmNpksexv", TESTNET));
}
@Test(expected = IllegalArgumentException.class)
public void testSegwitAddressCheckThrow() {
MAINNET.checkAddress(SegwitAddress.fromBech32("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", TESTNET));
}
}