From 68f65485053aca5e23892c826e50d7fead6cd9da Mon Sep 17 00:00:00 2001
From: Andreas Schildbach
Date: Thu, 15 Feb 2018 14:02:53 +0100
Subject: [PATCH] Implement native segwit addresses in new SegwitAddress class.
This commit establishes a base class for legacy and segwit addresses.
Uses Bech32 code from https://github.com/sipa/bech32/pull/40.
---
.../org/bitcoinj/core/AbstractAddress.java | 83 ++++++
.../main/java/org/bitcoinj/core/Address.java | 47 +++-
.../main/java/org/bitcoinj/core/Bech32.java | 152 +++++++++++
.../org/bitcoinj/core/DumpedPrivateKey.java | 15 +-
.../org/bitcoinj/core/NetworkParameters.java | 6 +
.../java/org/bitcoinj/core/SegwitAddress.java | 255 ++++++++++++++++++
.../core/VersionedChecksummedBytes.java | 15 --
.../bitcoinj/core/WrongNetworkException.java | 9 +-
.../org/bitcoinj/crypto/BIP38PrivateKey.java | 15 +-
.../org/bitcoinj/params/MainNetParams.java | 1 +
.../org/bitcoinj/params/TestNet2Params.java | 1 +
.../org/bitcoinj/params/TestNet3Params.java | 1 +
.../main/java/org/bitcoinj/script/Script.java | 11 +-
.../org/bitcoinj/script/ScriptBuilder.java | 47 ++--
.../java/org/bitcoinj/core/AddressTest.java | 1 -
.../java/org/bitcoinj/core/Bech32Test.java | 78 ++++++
.../org/bitcoinj/core/SegwitAddressTest.java | 175 ++++++++++++
.../core/VersionedChecksummedBytesTest.java | 4 +-
18 files changed, 864 insertions(+), 52 deletions(-)
create mode 100644 core/src/main/java/org/bitcoinj/core/AbstractAddress.java
create mode 100644 core/src/main/java/org/bitcoinj/core/Bech32.java
create mode 100644 core/src/main/java/org/bitcoinj/core/SegwitAddress.java
create mode 100644 core/src/test/java/org/bitcoinj/core/Bech32Test.java
create mode 100644 core/src/test/java/org/bitcoinj/core/SegwitAddressTest.java
diff --git a/core/src/main/java/org/bitcoinj/core/AbstractAddress.java b/core/src/main/java/org/bitcoinj/core/AbstractAddress.java
new file mode 100644
index 00000000000..d8a5560e211
--- /dev/null
+++ b/core/src/main/java/org/bitcoinj/core/AbstractAddress.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 Andreas Schildbach
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.bitcoinj.core;
+
+import javax.annotation.Nullable;
+
+import org.bitcoinj.script.Script.ScriptType;
+
+/**
+ *
+ * Base class for addresses, e.g. native segwit addresses ({@link SegwitAddress}) or legacy addresses ({@link Address}).
+ *
+ *
+ *
+ * Use {@link #fromString(NetworkParameters, String)} to conveniently construct any kind of address from its textual
+ * form.
+ *
+ */
+public abstract class AbstractAddress extends VersionedChecksummedBytes {
+ public AbstractAddress(NetworkParameters params, byte[] bytes) {
+ super(params, bytes);
+ }
+
+ /**
+ * Construct an address from its textual form.
+ *
+ * @param params
+ * the expected network this address is valid for, or null if the network should be derived from the
+ * textual form
+ * @param str
+ * the textual form of the address, such as "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL" or
+ * "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
+ * @return constructed address
+ * @throws AddressFormatException
+ * if the given string doesn't parse or the checksum is invalid
+ * @throws WrongNetworkException
+ * if the given string is valid but not for the expected network (eg testnet vs mainnet)
+ */
+ public static AbstractAddress fromString(@Nullable NetworkParameters params, String str)
+ throws AddressFormatException {
+ try {
+ return Address.fromBase58(params, str);
+ } catch (WrongNetworkException x) {
+ throw x;
+ } catch (AddressFormatException x) {
+ try {
+ return SegwitAddress.fromBech32(params, str);
+ } catch (WrongNetworkException x2) {
+ throw x;
+ } catch (AddressFormatException x2) {
+ throw new AddressFormatException(str);
+ }
+ }
+ }
+
+ /**
+ * Get either the public key hash or script hash that is encoded in the address.
+ *
+ * @return hash that is encoded in the address
+ */
+ public abstract 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();
+}
diff --git a/core/src/main/java/org/bitcoinj/core/Address.java b/core/src/main/java/org/bitcoinj/core/Address.java
index 6e6a8870325..75d80723c02 100644
--- a/core/src/main/java/org/bitcoinj/core/Address.java
+++ b/core/src/main/java/org/bitcoinj/core/Address.java
@@ -26,6 +26,7 @@
import org.bitcoinj.params.Networks;
import org.bitcoinj.script.Script;
+import org.bitcoinj.script.Script.ScriptType;
import org.bitcoinj.script.ScriptPattern;
import com.google.common.base.Objects;
@@ -41,7 +42,7 @@
* 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.
*/
-public class Address extends VersionedChecksummedBytes {
+public class Address extends AbstractAddress {
/**
* An address is a RIPEMD160 hash of a public key, therefore is always 160 bits or 20 bytes.
*/
@@ -167,20 +168,53 @@ public Address(NetworkParameters params, byte[] hash160) {
this(params, false, hash160);
}
- @Override
- protected int getVersion() {
+ /**
+ * Get the version header of an address. This is the first byte of a base58 encoded address.
+ *
+ * @return version header as one byte
+ */
+ public int getVersion() {
return p2sh ? params.getP2SHHeader() : params.getAddressHeader();
}
- /** The (big endian) 20 byte hash that is the core of a Bitcoin address. */
+ /**
+ * Returns the base58-encoded textual form, including version and checksum bytes.
+ *
+ * @return textual form
+ */
+ public String toBase58() {
+ return Base58.encodeChecked(getVersion(), bytes);
+ }
+
+ /** @deprecated use {@link #getHash()} */
+ @Deprecated
public byte[] getHash160() {
+ return getHash();
+ }
+
+ /** The (big endian) 20 byte hash that is the core of a Bitcoin address. */
+ @Override
+ public byte[] getHash() {
return bytes;
}
+ /**
+ * Get the type of output script that will be used for sending to the address. This is either
+ * {@link ScriptType#P2PKH} or {@link ScriptType#P2SH}.
+ *
+ * @return type of output script
+ */
+ @Override
+ public ScriptType getOutputScriptType() {
+ return p2sh ? ScriptType.P2SH : ScriptType.P2PKH;
+ }
+
/**
* Returns true if this address is a Pay-To-Script-Hash (P2SH) address.
* See also https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki: Address Format for pay-to-script-hash
+ * @deprecated Use {@link #getOutputScriptType()}
*/
+ @Deprecated
public boolean isP2SHAddress() {
return p2sh;
}
@@ -216,6 +250,11 @@ public int hashCode() {
return Objects.hashCode(super.hashCode(), p2sh);
}
+ @Override
+ public String toString() {
+ return toBase58();
+ }
+
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
diff --git a/core/src/main/java/org/bitcoinj/core/Bech32.java b/core/src/main/java/org/bitcoinj/core/Bech32.java
new file mode 100644
index 00000000000..22b5ec37a71
--- /dev/null
+++ b/core/src/main/java/org/bitcoinj/core/Bech32.java
@@ -0,0 +1,152 @@
+/* Copyright (c) 2018 Coinomi Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.bitcoinj.core;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+public class Bech32 {
+ /** The Bech32 character set for encoding. */
+ private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
+
+ /** The Bech32 character set for decoding. */
+ private static final byte[] CHARSET_REV = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
+ -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
+ 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
+ -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
+ 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
+ };
+
+ public static class Bech32Data {
+ final String hrp;
+ final byte[] data;
+
+ private Bech32Data(final String hrp, final byte[] data) {
+ this.hrp = hrp;
+ this.data = data;
+ }
+ }
+
+ /** Find the polynomial with value coefficients mod the generator as 30-bit. */
+ private static int polymod(final byte[] values) {
+ int c = 1;
+ for (byte v_i: values) {
+ int c0 = (c >>> 25) & 0xff;
+ c = ((c & 0x1ffffff) << 5) ^ (v_i & 0xff);
+ if ((c0 & 1) != 0) c ^= 0x3b6a57b2;
+ if ((c0 & 2) != 0) c ^= 0x26508e6d;
+ if ((c0 & 4) != 0) c ^= 0x1ea119fa;
+ if ((c0 & 8) != 0) c ^= 0x3d4233dd;
+ if ((c0 & 16) != 0) c ^= 0x2a1462b3;
+ }
+ return c;
+ }
+
+ /** Expand a HRP for use in checksum computation. */
+ private static byte[] expandHrp(final String hrp) {
+ int hrpLength = hrp.length();
+ byte ret[] = new byte[hrpLength * 2 + 1];
+ for (int i = 0; i < hrpLength; ++i) {
+ int c = hrp.charAt(i) & 0x7f; // Limit to standard 7-bit ASCII
+ ret[i] = (byte) ((c >>> 5) & 0x07);
+ ret[i + hrpLength + 1] = (byte) (c & 0x1f);
+ }
+ ret[hrpLength] = 0;
+ return ret;
+ }
+
+ /** Verify a checksum. */
+ private static boolean verifyChecksum(final String hrp, final byte[] values) {
+ byte[] hrpExpanded = expandHrp(hrp);
+ byte[] combined = new byte[hrpExpanded.length + values.length];
+ System.arraycopy(hrpExpanded, 0, combined, 0, hrpExpanded.length);
+ System.arraycopy(values, 0, combined, hrpExpanded.length, values.length);
+ return polymod(combined) == 1;
+ }
+
+ /** Create a checksum. */
+ private static byte[] createChecksum(final String hrp, final byte[] values) {
+ byte[] hrpExpanded = expandHrp(hrp);
+ byte[] enc = new byte[hrpExpanded.length + values.length + 6];
+ System.arraycopy(hrpExpanded, 0, enc, 0, hrpExpanded.length);
+ System.arraycopy(values, 0, enc, hrpExpanded.length, values.length);
+ int mod = polymod(enc) ^ 1;
+ byte[] ret = new byte[6];
+ for (int i = 0; i < 6; ++i) {
+ ret[i] = (byte) ((mod >>> (5 * (5 - i))) & 31);
+ }
+ return ret;
+ }
+
+ /** Encode a Bech32 string. */
+ public static String encode(final Bech32Data bech32) throws AddressFormatException {
+ return encode(bech32.hrp, bech32.data);
+ }
+
+ /** Encode a Bech32 string. */
+ public static String encode(String hrp, final byte[] values) throws AddressFormatException {
+ if (hrp.length() < 1) throw new AddressFormatException("Human-readable part is too short");
+ if (hrp.length() > 83) throw new AddressFormatException("Human-readable part is too long");
+ hrp = hrp.toLowerCase(Locale.ROOT);
+ byte[] checksum = createChecksum(hrp, values);
+ byte[] combined = new byte[values.length + checksum.length];
+ System.arraycopy(values, 0, combined, 0, values.length);
+ System.arraycopy(checksum, 0, combined, values.length, checksum.length);
+ StringBuilder sb = new StringBuilder(hrp.length() + 1 + combined.length);
+ sb.append(hrp);
+ sb.append('1');
+ for (byte b : combined) {
+ sb.append(CHARSET.charAt(b));
+ }
+ return sb.toString();
+ }
+
+ /** Decode a Bech32 string. */
+ public static Bech32Data decode(final String str) throws AddressFormatException {
+ boolean lower = false, upper = false;
+ if (str.length() < 8) throw new AddressFormatException("Input too short");
+ if (str.length() > 90) throw new AddressFormatException("Input too long");
+ for (int i = 0; i < str.length(); ++i) {
+ char c = str.charAt(i);
+ if (c < 33 || c > 126) throw new AddressFormatException("Characters out of range");
+ if (c >= 'a' && c <= 'z') lower = true;
+ if (c >= 'A' && c <= 'Z') upper = true;
+ }
+ if (lower && upper) throw new AddressFormatException("Cannot mix upper and lower cases");
+ int pos = str.lastIndexOf('1');
+ if (pos < 1) throw new AddressFormatException("Missing human-readable part");
+ if (pos + 7 > str.length()) throw new AddressFormatException("Data part too short");
+ byte[] values = new byte[str.length() - 1 - pos];
+ for (int i = 0; i < str.length() - 1 - pos; ++i) {
+ char c = str.charAt(i + pos + 1);
+ if (CHARSET_REV[c] == -1) throw new AddressFormatException("Characters out of range");
+ values[i] = CHARSET_REV[c];
+ }
+ String hrp = str.substring(0, pos).toLowerCase(Locale.ROOT);
+ if (!verifyChecksum(hrp, values)) throw new AddressFormatException("Invalid checksum");
+ return new Bech32Data(hrp, Arrays.copyOfRange(values, 0, values.length - 6));
+ }
+}
diff --git a/core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java b/core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java
index 25a6726017b..77bc241c9f9 100644
--- a/core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java
+++ b/core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java
@@ -71,9 +71,13 @@ private DumpedPrivateKey(NetworkParameters params, byte[] bytes) {
this(params, encode(keyBytes, compressed));
}
- @Override
- protected int getVersion() {
- return params.getDumpedPrivateKeyHeader();
+ /**
+ * Returns the base58-encoded textual form, including version and checksum bytes.
+ *
+ * @return textual form
+ */
+ public String toBase58() {
+ return Base58.encodeChecked(params.getDumpedPrivateKeyHeader(), bytes);
}
private static byte[] encode(byte[] keyBytes, boolean compressed) {
@@ -102,4 +106,9 @@ public ECKey getKey() {
public boolean isPubKeyCompressed() {
return bytes.length == 33 && bytes[32] == 1;
}
+
+ @Override
+ public String toString() {
+ return toBase58();
+ }
}
diff --git a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java
index 10eb323f8f6..ca2810f4f2f 100644
--- a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java
+++ b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java
@@ -77,6 +77,7 @@ public abstract class NetworkParameters {
protected int addressHeader;
protected int p2shHeader;
protected int dumpedPrivateKeyHeader;
+ protected String segwitAddressHrp;
protected int interval;
protected int targetTimespan;
protected byte[] alertSigningKey;
@@ -336,6 +337,11 @@ public int getDumpedPrivateKeyHeader() {
return dumpedPrivateKeyHeader;
}
+ /** Human readable part of bech32 encoded segwit address. */
+ public String getSegwitAddressHrp() {
+ return segwitAddressHrp;
+ }
+
/**
* How much time in seconds is supposed to pass between "interval" blocks. If the actual elapsed time is
* significantly different from this value, the network difficulty formula will produce a different value. Both
diff --git a/core/src/main/java/org/bitcoinj/core/SegwitAddress.java b/core/src/main/java/org/bitcoinj/core/SegwitAddress.java
new file mode 100644
index 00000000000..92368e894de
--- /dev/null
+++ b/core/src/main/java/org/bitcoinj/core/SegwitAddress.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2018 Andreas Schildbach
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.bitcoinj.core;
+
+import java.io.ByteArrayOutputStream;
+
+import javax.annotation.Nullable;
+
+import org.bitcoinj.params.Networks;
+import org.bitcoinj.script.Script;
+import org.bitcoinj.script.Script.ScriptType;
+
+/**
+ *
+ * Implementation of native segwit addresses. They are composed of two parts:
+ *
+ * - A human-readable part (HRP) which is a string the specifies the network. See
+ * {@link NetworkParameters#getSegwitAddressHrp()}.
+ * - A data part, containing the witness version (encoded as an OP_N operator) and program (encoded by re-arranging
+ * bits into groups of 5).
+ *
+ * See BIP173 for details.
+ *
+ *
+ *
+ * However, you don't need to care about the internals. Use {@link #fromBech32(NetworkParameters, String)},
+ * {@link #fromHash(NetworkParameters, byte[])} or {@link #fromKey(NetworkParameters, ECKey)} to construct a native
+ * segwit address.
+ *
+ */
+public class SegwitAddress extends AbstractAddress {
+ public static final int WITNESS_PROGRAM_LENGTH_PKH = 20;
+ public static final int WITNESS_PROGRAM_LENGTH_SH = 32;
+ public static final int WITNESS_PROGRAM_MIN_LENGTH = 2;
+ public static final int WITNESS_PROGRAM_MAX_LENGTH = 40;
+
+ /**
+ * Private constructor. Use {@link #fromBech32(NetworkParameters, String)},
+ * {@link #fromHash(NetworkParameters, byte[])} or {@link #fromKey(NetworkParameters, ECKey)}.
+ *
+ * @param params
+ * network this address is valid for
+ * @param witnessVersion
+ * version number between 0 and 16
+ * @param witnessProgram
+ * hash of pubkey or script (for version 0)
+ */
+ private SegwitAddress(NetworkParameters params, int witnessVersion, byte[] witnessProgram)
+ throws AddressFormatException {
+ this(params, encode(witnessVersion, witnessProgram));
+ }
+
+ /**
+ * Helper for the above constructor.
+ */
+ private static byte[] encode(int witnessVersion, byte[] witnessProgram) throws AddressFormatException {
+ byte[] convertedProgram = convertBits(witnessProgram, 0, witnessProgram.length, 8, 5, true);
+ byte[] bytes = new byte[1 + convertedProgram.length];
+ bytes[0] = (byte) (Script.encodeToOpN(witnessVersion) & 0xff);
+ System.arraycopy(convertedProgram, 0, bytes, 1, convertedProgram.length);
+ return bytes;
+ }
+
+ /**
+ * Private constructor. Use {@link #fromBech32(NetworkParameters, String)},
+ * {@link #fromHash(NetworkParameters, byte[])} or {@link #fromKey(NetworkParameters, ECKey)}.
+ *
+ * @param params
+ * network this address is valid for
+ * @param data
+ * in segwit address format, before bit re-arranging and bech32 encoding
+ * @throws AddressFormatException
+ * if any of the sanity checks fail
+ */
+ private SegwitAddress(NetworkParameters params, byte[] data) throws AddressFormatException {
+ super(params, data);
+ if (data.length < 1)
+ throw new AddressFormatException("Zero data found");
+ final int witnessVersion = getWitnessVersion();
+ if (witnessVersion < 0 || witnessVersion > 16)
+ throw new AddressFormatException("Invalid script version: " + witnessVersion);
+ byte[] witnessProgram = getWitnessProgram();
+ if (witnessProgram.length < WITNESS_PROGRAM_MIN_LENGTH || witnessProgram.length > WITNESS_PROGRAM_MAX_LENGTH)
+ throw new AddressFormatException("Invalid length: " + witnessProgram.length);
+ // Check script length for version 0
+ if (witnessVersion == 0 && witnessProgram.length != WITNESS_PROGRAM_LENGTH_PKH
+ && witnessProgram.length != WITNESS_PROGRAM_LENGTH_SH)
+ throw new AddressFormatException("Invalid length for address version 0: " + witnessProgram.length);
+ }
+
+ /**
+ * Returns the witness version in decoded form. Only version 0 is in use right now.
+ *
+ * @return witness version, between 0 and 16
+ */
+ public int getWitnessVersion() {
+ return bytes[0] & 0xff;
+ }
+
+ /**
+ * Returns the witness program in decoded form.
+ *
+ * @return witness program
+ */
+ public byte[] getWitnessProgram() {
+ // skip version byte
+ return convertBits(bytes, 1, bytes.length - 1, 5, 8, false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public byte[] getHash() {
+ return getWitnessProgram();
+ }
+
+ /**
+ * Get the type of output script that will be used for sending to the address. This is either
+ * {@link ScriptType#P2WPKH} or {@link ScriptType#P2WSH}.
+ *
+ * @return type of output script
+ */
+ @Override
+ public ScriptType getOutputScriptType() {
+ int version = getWitnessVersion();
+ if (version != 0)
+ return ScriptType.NO_TYPE;
+ int programLength = getWitnessProgram().length;
+ if (programLength == WITNESS_PROGRAM_LENGTH_PKH)
+ return ScriptType.P2WPKH;
+ else if (programLength == WITNESS_PROGRAM_LENGTH_SH)
+ return ScriptType.P2WSH;
+ else
+ return ScriptType.NO_TYPE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return toBech32();
+ }
+
+ /**
+ * Construct a {@link SegwitAddress} from its textual form.
+ *
+ * @param params
+ * expected network this address is valid for, or null if the network should be derived from the bech32
+ * @param bech32
+ * bech32-encoded textual form of the address
+ * @return constructed address
+ * @throws AddressFormatException
+ * if something about the given bech32 address isn't right
+ */
+ public static SegwitAddress fromBech32(@Nullable NetworkParameters params, String bech32)
+ throws AddressFormatException {
+ Bech32.Bech32Data bechData = Bech32.decode(bech32);
+ if (params == null) {
+ for (NetworkParameters p : Networks.get()) {
+ if (bechData.hrp.equals(p.getSegwitAddressHrp()))
+ return new SegwitAddress(p, bechData.data);
+ }
+ throw new AddressFormatException("No network found for " + bech32);
+ } else {
+ if (bechData.hrp.equals(params.getSegwitAddressHrp()))
+ return new SegwitAddress(params, bechData.data);
+ throw new WrongNetworkException(bechData.hrp);
+ }
+ }
+
+ /**
+ * Construct a {@link SegwitAddress} that represents the given hash, which is either a pubkey hash or a script hash.
+ * The resulting address will be either a P2WPKH or a P2WSH type of address.
+ *
+ * @param params
+ * network this address is valid for
+ * @param hash
+ * 20-byte pubkey hash or 32-byte script hash
+ * @return constructed address
+ */
+ public static SegwitAddress fromHash(NetworkParameters params, byte[] hash) {
+ return new SegwitAddress(params, 0, hash);
+ }
+
+ /**
+ * Construct a {@link SegwitAddress} that represents the public part of the given {@link ECKey}. Note that an
+ * address is derived from a hash of the public key and is not the public key itself.
+ *
+ * @param params
+ * network this address is valid for
+ * @param key
+ * only the public part is used
+ * @return constructed address
+ */
+ public static SegwitAddress fromKey(NetworkParameters params, ECKey key) {
+ return fromHash(params, key.getPubKeyHash());
+ }
+
+ /**
+ * Returns the textual form of the address.
+ *
+ * @return textual form encoded in bech32
+ */
+ public String toBech32() {
+ return Bech32.encode(params.getSegwitAddressHrp(), bytes);
+ }
+
+ /**
+ * Helper for re-arranging bits into groups.
+ */
+ private static byte[] convertBits(final byte[] in, final int inStart, final int inLen, final int fromBits,
+ final int toBits, final boolean pad) throws AddressFormatException {
+ int acc = 0;
+ int bits = 0;
+ ByteArrayOutputStream out = new ByteArrayOutputStream(64);
+ final int maxv = (1 << toBits) - 1;
+ final int max_acc = (1 << (fromBits + toBits - 1)) - 1;
+ for (int i = 0; i < inLen; i++) {
+ int value = in[i + inStart] & 0xff;
+ if ((value >>> fromBits) != 0) {
+ throw new AddressFormatException(
+ String.format("Input value '%X' exceeds '%d' bit size", value, fromBits));
+ }
+ acc = ((acc << fromBits) | value) & max_acc;
+ bits += fromBits;
+ while (bits >= toBits) {
+ bits -= toBits;
+ out.write((acc >>> bits) & maxv);
+ }
+ }
+ if (pad) {
+ if (bits > 0)
+ out.write((acc << (toBits - bits)) & maxv);
+ } else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv) != 0) {
+ throw new AddressFormatException("Could not convert bits, invalid padding");
+ }
+ return out.toByteArray();
+ }
+}
diff --git a/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java b/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java
index 3973827306a..3b0f8a76fb8 100644
--- a/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java
+++ b/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java
@@ -57,21 +57,6 @@ public final NetworkParameters getParameters() {
return params;
}
- /**
- * Returns the base-58 encoded String representation of this
- * object, including version and checksum bytes.
- */
- public final String toBase58() {
- return Base58.encodeChecked(getVersion(), bytes);
- }
-
- protected abstract int getVersion();
-
- @Override
- public String toString() {
- return toBase58();
- }
-
@Override
public int hashCode() {
return Objects.hashCode(params, Arrays.hashCode(bytes));
diff --git a/core/src/main/java/org/bitcoinj/core/WrongNetworkException.java b/core/src/main/java/org/bitcoinj/core/WrongNetworkException.java
index a875ba4f029..22b60bb2ec6 100644
--- a/core/src/main/java/org/bitcoinj/core/WrongNetworkException.java
+++ b/core/src/main/java/org/bitcoinj/core/WrongNetworkException.java
@@ -1,5 +1,6 @@
/*
* Copyright 2012 Google Inc.
+ * Copyright 2018 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,11 +23,11 @@
* different chains, an operation that is guaranteed to destroy the money.
*/
public class WrongNetworkException extends AddressFormatException {
- /** The version code that was provided in the address. */
- public int verCode;
-
public WrongNetworkException(int verCode) {
super("Version code of address did not match acceptable versions for network: " + verCode);
- this.verCode = verCode;
+ }
+
+ public WrongNetworkException(String hrp) {
+ super("Human readable part of address did not match acceptable HRPs for network: " + hrp);
}
}
diff --git a/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java b/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java
index b4dc55b31aa..1f942a3ef32 100644
--- a/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java
+++ b/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java
@@ -105,9 +105,13 @@ private BIP38PrivateKey(NetworkParameters params, byte[] bytes, boolean ecMultip
this.content = content;
}
- @Override
- protected int getVersion() {
- return 1;
+ /**
+ * Returns the base58-encoded textual form, including version and checksum bytes.
+ *
+ * @return textual form
+ */
+ public String toBase58() {
+ return Base58.encodeChecked(1, bytes);
}
public ECKey decrypt(String passphrase) throws BadPassphraseException {
@@ -186,4 +190,9 @@ private ECKey decryptEC(String normalizedPassphrase) {
throw new RuntimeException(x);
}
}
+
+ @Override
+ public String toString() {
+ return toBase58();
+ }
}
diff --git a/core/src/main/java/org/bitcoinj/params/MainNetParams.java b/core/src/main/java/org/bitcoinj/params/MainNetParams.java
index 194ff7738cd..738c50df589 100644
--- a/core/src/main/java/org/bitcoinj/params/MainNetParams.java
+++ b/core/src/main/java/org/bitcoinj/params/MainNetParams.java
@@ -40,6 +40,7 @@ public MainNetParams() {
dumpedPrivateKeyHeader = 128;
addressHeader = 0;
p2shHeader = 5;
+ segwitAddressHrp = "bc";
port = 8333;
packetMagic = 0xf9beb4d9L;
bip32HeaderPub = 0x0488B21E; //The 4 byte header that serializes in base58 to "xpub".
diff --git a/core/src/main/java/org/bitcoinj/params/TestNet2Params.java b/core/src/main/java/org/bitcoinj/params/TestNet2Params.java
index 4b1755e3cca..cf1ee44089b 100644
--- a/core/src/main/java/org/bitcoinj/params/TestNet2Params.java
+++ b/core/src/main/java/org/bitcoinj/params/TestNet2Params.java
@@ -40,6 +40,7 @@ public TestNet2Params() {
targetTimespan = TARGET_TIMESPAN;
maxTarget = Utils.decodeCompactBits(0x1d0fffffL);
dumpedPrivateKeyHeader = 239;
+ segwitAddressHrp = "tb";
genesisBlock.setTime(1296688602L);
genesisBlock.setDifficultyTarget(0x1d07fff8L);
genesisBlock.setNonce(384568319);
diff --git a/core/src/main/java/org/bitcoinj/params/TestNet3Params.java b/core/src/main/java/org/bitcoinj/params/TestNet3Params.java
index e9a3472c7d1..051f6002dc2 100644
--- a/core/src/main/java/org/bitcoinj/params/TestNet3Params.java
+++ b/core/src/main/java/org/bitcoinj/params/TestNet3Params.java
@@ -47,6 +47,7 @@ public TestNet3Params() {
addressHeader = 111;
p2shHeader = 196;
dumpedPrivateKeyHeader = 239;
+ segwitAddressHrp = "tb";
genesisBlock.setTime(1296688602L);
genesisBlock.setDifficultyTarget(0x1d00ffffL);
genesisBlock.setNonce(414098458);
diff --git a/core/src/main/java/org/bitcoinj/script/Script.java b/core/src/main/java/org/bitcoinj/script/Script.java
index d08e316041f..a32b72a930f 100644
--- a/core/src/main/java/org/bitcoinj/script/Script.java
+++ b/core/src/main/java/org/bitcoinj/script/Script.java
@@ -60,7 +60,9 @@ public enum ScriptType {
NO_TYPE,
P2PKH,
PUB_KEY,
- P2SH
+ P2SH,
+ P2WPKH,
+ P2WSH
}
/** Flags to pass to {@link Script#correctlySpends(Transaction, long, Script, Set)}.
@@ -507,8 +509,9 @@ private static int getSigOpCount(List chunks, boolean accurate) thr
return sigOps;
}
- static int decodeFromOpN(int opcode) {
- checkArgument((opcode == OP_0 || opcode == OP_1NEGATE) || (opcode >= OP_1 && opcode <= OP_16), "decodeFromOpN called on non OP_N opcode");
+ public static int decodeFromOpN(int opcode) {
+ checkArgument((opcode == OP_0 || opcode == OP_1NEGATE) || (opcode >= OP_1 && opcode <= OP_16),
+ "decodeFromOpN called on non OP_N opcode: %s", ScriptOpCodes.getOpCodeName(opcode));
if (opcode == OP_0)
return 0;
else if (opcode == OP_1NEGATE)
@@ -517,7 +520,7 @@ else if (opcode == OP_1NEGATE)
return opcode + 1 - OP_1;
}
- static int encodeToOpN(int value) {
+ public static int encodeToOpN(int value) {
checkArgument(value >= -1 && value <= 16, "encodeToOpN called for " + value + " which we cannot encode in an opcode.");
if (value == 0)
return OP_0;
diff --git a/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java b/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java
index efca88cb227..e847835c33f 100644
--- a/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java
+++ b/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java
@@ -18,10 +18,14 @@
package org.bitcoinj.script;
import com.google.common.collect.Lists;
+
+import org.bitcoinj.core.AbstractAddress;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.ECKey;
+import org.bitcoinj.core.SegwitAddress;
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.TransactionSignature;
+import org.bitcoinj.script.Script.ScriptType;
import javax.annotation.Nullable;
import java.math.BigInteger;
@@ -248,24 +252,35 @@ public Script build() {
}
/** Creates a scriptPubKey that encodes payment to the given address. */
- public static Script createOutputScript(Address to) {
- if (to.isP2SHAddress()) {
- // OP_HASH160 OP_EQUAL
- return new ScriptBuilder()
- .op(OP_HASH160)
- .data(to.getHash160())
- .op(OP_EQUAL)
- .build();
+ public static Script createOutputScript(AbstractAddress to) {
+ ScriptBuilder builder = new ScriptBuilder();
+ if (to instanceof Address) {
+ Address toLegacy = (Address) to;
+ ScriptType scriptType = toLegacy.getOutputScriptType();
+ if (scriptType == ScriptType.P2PKH) {
+ // OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG
+ builder.op(OP_DUP);
+ builder.op(OP_HASH160);
+ builder.data(toLegacy.getHash());
+ builder.op(OP_EQUALVERIFY);
+ builder.op(OP_CHECKSIG);
+ } else if (scriptType == ScriptType.P2SH) {
+ // OP_HASH160 OP_EQUAL
+ builder.op(OP_HASH160);
+ builder.data(toLegacy.getHash());
+ builder.op(OP_EQUAL);
+ } else {
+ throw new IllegalStateException("Cannot handle " + scriptType);
+ }
+ } else if (to instanceof SegwitAddress) {
+ // OP_0
+ SegwitAddress toSegwit = (SegwitAddress) to;
+ builder.smallNum(toSegwit.getWitnessVersion());
+ builder.data(toSegwit.getWitnessProgram());
} else {
- // OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG
- return new ScriptBuilder()
- .op(OP_DUP)
- .op(OP_HASH160)
- .data(to.getHash160())
- .op(OP_EQUALVERIFY)
- .op(OP_CHECKSIG)
- .build();
+ throw new IllegalStateException("Cannot handle " + to);
}
+ return builder.build();
}
/** Creates a scriptPubKey that encodes payment to the given raw public key. */
diff --git a/core/src/test/java/org/bitcoinj/core/AddressTest.java b/core/src/test/java/org/bitcoinj/core/AddressTest.java
index 54c5e67de56..21381d34eac 100644
--- a/core/src/test/java/org/bitcoinj/core/AddressTest.java
+++ b/core/src/test/java/org/bitcoinj/core/AddressTest.java
@@ -104,7 +104,6 @@ public void errorPaths() {
fail();
} catch (WrongNetworkException e) {
// Success.
- assertEquals(e.verCode, MainNetParams.get().getAddressHeader());
} catch (AddressFormatException e) {
fail();
}
diff --git a/core/src/test/java/org/bitcoinj/core/Bech32Test.java b/core/src/test/java/org/bitcoinj/core/Bech32Test.java
new file mode 100644
index 00000000000..02fd331c553
--- /dev/null
+++ b/core/src/test/java/org/bitcoinj/core/Bech32Test.java
@@ -0,0 +1,78 @@
+/* Copyright (c) 2018 Coinomi Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.bitcoinj.core;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.Locale;
+
+import org.junit.Test;
+
+public class Bech32Test {
+ @Test
+ public void validChecksum() {
+ for (String valid : VALID_CHECKSUMS) {
+ Bech32.Bech32Data bechData = Bech32.decode(valid);
+ String recode = Bech32.encode(bechData);
+ assertEquals(String.format("Failed to roundtrip '%s' -> '%s'", valid, recode),
+ valid.toLowerCase(Locale.ROOT), recode.toLowerCase(Locale.ROOT));
+ // Test encoding with an uppercase HRP
+ recode = Bech32.encode(bechData.hrp.toUpperCase(Locale.ROOT), bechData.data);
+ assertEquals(String.format("Failed to roundtrip '%s' -> '%s'", valid, recode),
+ valid.toLowerCase(Locale.ROOT), recode.toLowerCase(Locale.ROOT));
+ }
+ }
+
+ @Test
+ public void invalidChecksum() {
+ for (String invalid : INVALID_CHECKSUMS) {
+ try {
+ Bech32.decode(invalid);
+ fail(String.format("Parsed an invalid code: '%s'", invalid));
+ } catch (AddressFormatException x) {
+ /* expected */
+ }
+ }
+ }
+
+ // test vectors
+
+ private static String[] VALID_CHECKSUMS = {
+ "A12UEL5L",
+ "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
+ "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
+ "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
+ "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"
+ };
+
+ private static String[] INVALID_CHECKSUMS = {
+ " 1nwldj5",
+ new String(new char[] { 0x7f }) + "1axkwrx",
+ "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx",
+ "pzry9x0s0muk",
+ "1pzry9x0s0muk",
+ "x1b4n0q5v",
+ "li1dgmt3",
+ "de1lg7wt" + new String(new char[] { 0xff }),
+ };
+}
diff --git a/core/src/test/java/org/bitcoinj/core/SegwitAddressTest.java b/core/src/test/java/org/bitcoinj/core/SegwitAddressTest.java
new file mode 100644
index 00000000000..070577102fa
--- /dev/null
+++ b/core/src/test/java/org/bitcoinj/core/SegwitAddressTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2018 Andreas Schildbach
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.bitcoinj.core;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Locale;
+
+import org.bitcoinj.params.MainNetParams;
+import org.bitcoinj.params.TestNet3Params;
+import org.bitcoinj.script.Script.ScriptType;
+import org.bitcoinj.script.ScriptBuilder;
+import org.junit.Test;
+
+import com.google.common.base.MoreObjects;
+
+public class SegwitAddressTest {
+ private static final MainNetParams MAINNET = MainNetParams.get();
+ private static final TestNet3Params TESTNET = TestNet3Params.get();
+
+ @Test
+ public void example_p2wpkh_mainnet() {
+ String bech32 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
+
+ SegwitAddress address = SegwitAddress.fromBech32(MAINNET, bech32);
+
+ assertEquals(MAINNET, address.params);
+ assertEquals("0014751e76e8199196d454941c45d1b3a323f1433bd6",
+ Utils.HEX.encode(ScriptBuilder.createOutputScript(address).getProgram()));
+ assertEquals(ScriptType.P2WPKH, address.getOutputScriptType());
+ assertEquals(bech32.toLowerCase(Locale.ROOT), address.toBech32());
+ assertEquals(bech32.toLowerCase(Locale.ROOT), address.toString());
+ }
+
+ @Test
+ public void example_p2wsh_mainnet() {
+ String bech32 = "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3";
+
+ SegwitAddress address = SegwitAddress.fromBech32(MAINNET, bech32);
+
+ assertEquals(MAINNET, address.params);
+ assertEquals("00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",
+ Utils.HEX.encode(ScriptBuilder.createOutputScript(address).getProgram()));
+ assertEquals(ScriptType.P2WSH, address.getOutputScriptType());
+ assertEquals(bech32.toLowerCase(Locale.ROOT), address.toBech32());
+ assertEquals(bech32.toLowerCase(Locale.ROOT), address.toString());
+ }
+
+ @Test
+ public void example_p2wpkh_testnet() {
+ String bech32 = "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx";
+
+ SegwitAddress address = SegwitAddress.fromBech32(TESTNET, bech32);
+
+ assertEquals(TESTNET, address.params);
+ assertEquals("0014751e76e8199196d454941c45d1b3a323f1433bd6",
+ Utils.HEX.encode(ScriptBuilder.createOutputScript(address).getProgram()));
+ assertEquals(ScriptType.P2WPKH, address.getOutputScriptType());
+ assertEquals(bech32.toLowerCase(Locale.ROOT), address.toBech32());
+ assertEquals(bech32.toLowerCase(Locale.ROOT), address.toString());
+ }
+
+ @Test
+ public void example_p2wsh_testnet() {
+ String bech32 = "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7";
+
+ SegwitAddress address = SegwitAddress.fromBech32(TESTNET, bech32);
+
+ assertEquals(TESTNET, address.params);
+ assertEquals("00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",
+ Utils.HEX.encode(ScriptBuilder.createOutputScript(address).getProgram()));
+ assertEquals(ScriptType.P2WSH, address.getOutputScriptType());
+ assertEquals(bech32.toLowerCase(Locale.ROOT), address.toBech32());
+ assertEquals(bech32.toLowerCase(Locale.ROOT), address.toString());
+ }
+
+ @Test
+ public void validAddresses() {
+ for (AddressData valid : VALID_ADDRESSES) {
+ SegwitAddress address = SegwitAddress.fromBech32(null, valid.address);
+
+ assertEquals(valid.expectedParams, address.params);
+ assertEquals(valid.expectedScriptPubKey,
+ Utils.HEX.encode(ScriptBuilder.createOutputScript(address).getProgram()));
+ assertEquals(valid.address.toLowerCase(Locale.ROOT), address.toBech32());
+ assertEquals(valid.expectedWitnessVersion, address.getWitnessVersion());
+ }
+ }
+
+ private static class AddressData {
+ final String address;
+ final NetworkParameters expectedParams;
+ final String expectedScriptPubKey;
+ final int expectedWitnessVersion;
+
+ AddressData(String address, NetworkParameters expectedParams, String expectedScriptPubKey,
+ int expectedWitnessVersion) {
+ this.address = address;
+ this.expectedParams = expectedParams;
+ this.expectedScriptPubKey = expectedScriptPubKey;
+ this.expectedWitnessVersion = expectedWitnessVersion;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("address", address).add("params", expectedParams.getId())
+ .add("scriptPubKey", expectedScriptPubKey).add("witnessVersion", expectedWitnessVersion).toString();
+ }
+ }
+
+ private static AddressData[] VALID_ADDRESSES = {
+ new AddressData("BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", MAINNET,
+ "0014751e76e8199196d454941c45d1b3a323f1433bd6", 0),
+ new AddressData("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", TESTNET,
+ "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", 0),
+ new AddressData("bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", MAINNET,
+ "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6", 1),
+ new AddressData("BC1SW50QA3JX3S", MAINNET, "6002751e", 16),
+ new AddressData("bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", MAINNET, "5210751e76e8199196d454941c45d1b3a323", 2),
+ new AddressData("tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", TESTNET,
+ "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", 0) };
+
+ @Test
+ public void invalidAddresses() {
+ for (String invalid : INVALID_ADDRESSES) {
+ try {
+ SegwitAddress.fromBech32(null, invalid);
+ fail(invalid);
+ } catch (AddressFormatException x) {
+ // expected
+ }
+ }
+ }
+
+ private static String[] INVALID_ADDRESSES = { "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
+ "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", "bc1rw5uspcuh",
+ "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
+ "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
+ "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
+ "bc1gmk9yu" };
+
+ @Test
+ public void testJavaSerialization() throws Exception {
+ SegwitAddress address = SegwitAddress.fromBech32(null, "BC1SW50QA3JX3S");
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ new ObjectOutputStream(os).writeObject(address);
+ VersionedChecksummedBytes addressCopy = (VersionedChecksummedBytes) new ObjectInputStream(
+ new ByteArrayInputStream(os.toByteArray())).readObject();
+
+ assertEquals(address, addressCopy);
+ assertEquals(address.params, addressCopy.params);
+ assertArrayEquals(address.bytes, addressCopy.bytes);
+ }
+}
diff --git a/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java b/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java
index 4159a03873d..e9560ea5496 100644
--- a/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java
+++ b/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java
@@ -35,8 +35,8 @@ public VersionedChecksummedBytesToTest(NetworkParameters params, byte[] bytes) {
}
@Override
- protected int getVersion() {
- return params.getAddressHeader();
+ public String toString() {
+ return Base58.encodeChecked(params.getAddressHeader(), bytes);
}
}