diff --git a/core/src/main/java/org/bitcoinj/base/BitcoinNetwork.java b/core/src/main/java/org/bitcoinj/base/BitcoinNetwork.java index a5c16b73f..14e1b92c3 100644 --- a/core/src/main/java/org/bitcoinj/base/BitcoinNetwork.java +++ b/core/src/main/java/org/bitcoinj/base/BitcoinNetwork.java @@ -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". diff --git a/core/src/test/java/org/bitcoinj/base/BitcoinNetworkTest.java b/core/src/test/java/org/bitcoinj/base/BitcoinNetworkTest.java index fff4dc244..4db28657a 100644 --- a/core/src/test/java/org/bitcoinj/base/BitcoinNetworkTest.java +++ b/core/src/test/java/org/bitcoinj/base/BitcoinNetworkTest.java @@ -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)); + } }