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 000000000000..901c350f46db --- /dev/null +++ b/core/src/main/java/org/bitcoinj/core/AbstractAddress.java @@ -0,0 +1,58 @@ +/* + * 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; + +public abstract class AbstractAddress extends VersionedChecksummedBytes { + public AbstractAddress(NetworkParameters params, byte[] bytes) { + super(params, bytes); + } + + /** + * Construct an address from its textual representation. + * + * @param params + * The expected NetworkParameters or null if you don't want validation. + * @param str + * The textual form of the address, such as "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL". + * @throws AddressFormatException + * if the given string doesn't parse or the checksum is invalid + * @throws WrongNetworkException + * if the given string is valid but for a different chain (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); + } + } + } + + public abstract ScriptType getScriptType(); +} diff --git a/core/src/main/java/org/bitcoinj/core/Address.java b/core/src/main/java/org/bitcoinj/core/Address.java index 3fe04c11f8db..bcf3e45c7f82 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 com.google.common.base.Objects; @@ -40,7 +41,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. */ @@ -114,9 +115,11 @@ else if (version == params.getP2SHHeader()) } } - @Override - protected int getVersion() { - return p2sh ? params.getP2SHHeader() : params.getAddressHeader(); + /** + * Returns the base58-encoded textual representation, including version and checksum bytes. + */ + public String toBase58() { + return toBase58(p2sh ? params.getP2SHHeader() : params.getAddressHeader(), bytes); } /** The (big endian) 20 byte hash that is the core of a Bitcoin address. */ @@ -124,9 +127,15 @@ public byte[] getHash160() { return bytes; } + @Override + public ScriptType getScriptType() { + 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 #getScriptType()} */ public boolean isP2SHAddress() { return p2sh; @@ -162,6 +171,11 @@ public int hashCode() { return Objects.hashCode(super.hashCode(), p2sh); } + @Override + public String toString() { + return toBase58(); + } + /** * This implementation narrows the return type toAddress
.
*/
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 000000000000..dc23b92a06e0
--- /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[] values;
+
+ private Bech32Data(final String hrp, final byte[] values) {
+ this.hrp = hrp;
+ this.values = values;
+ }
+ }
+
+ /** 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.values);
+ }
+
+ /** 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 25a6726017b0..7a5e7004a69b 100644
--- a/core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java
+++ b/core/src/main/java/org/bitcoinj/core/DumpedPrivateKey.java
@@ -71,9 +71,11 @@ private DumpedPrivateKey(NetworkParameters params, byte[] bytes) {
this(params, encode(keyBytes, compressed));
}
- @Override
- protected int getVersion() {
- return params.getDumpedPrivateKeyHeader();
+ /**
+ * Returns the base58-encoded textual representation, including version and checksum bytes.
+ */
+ public String toBase58() {
+ return toBase58(params.getDumpedPrivateKeyHeader(), bytes);
}
private static byte[] encode(byte[] keyBytes, boolean compressed) {
@@ -102,4 +104,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 10eb323f8f65..ca2810f4f2f7 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 000000000000..909f9ce46742
--- /dev/null
+++ b/core/src/main/java/org/bitcoinj/core/SegwitAddress.java
@@ -0,0 +1,152 @@
+/*
+ * 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 java.io.IOException;
+
+import javax.annotation.Nullable;
+
+import org.bitcoinj.params.Networks;
+import org.bitcoinj.script.Script.ScriptType;
+
+public class SegwitAddress extends AbstractAddress {
+ private static final int WITNESS_PROGRAM_LENGTH_SH = 32;
+ private static final int WITNESS_PROGRAM_LENGTH_PKH = 20;
+
+ public SegwitAddress(NetworkParameters params, int witnessVersion, byte[] witnessProgram)
+ throws AddressFormatException {
+ this(params, encode(witnessVersion, witnessProgram));
+ }
+
+ private static byte[] encode(int witnessVersion, byte[] witnessProgram) throws AddressFormatException {
+ try {
+ ByteArrayOutputStream enc = new ByteArrayOutputStream(64 + 1);
+ enc.write(witnessVersion);
+ enc.write(convertBits(witnessProgram, 0, witnessProgram.length, 8, 5, true));
+ return enc.toByteArray();
+ } catch (IOException x) {
+ throw new RuntimeException(x);
+ }
+ }
+
+ private SegwitAddress(NetworkParameters params, byte[] bytes) {
+ super(params, bytes);
+ if (bytes.length < 1)
+ throw new AddressFormatException("Zero data found");
+ final int witnessVersion = witnessVersion();
+ if (witnessVersion > 16)
+ throw new AddressFormatException("Invalid script version");
+ byte[] witnessProgram = witnessProgram();
+ if (witnessProgram.length < 2 || witnessProgram.length > 40)
+ throw new AddressFormatException("Invalid 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");
+ }
+
+ private int witnessVersion() {
+ return bytes[0] & 0xff;
+ }
+
+ private byte[] witnessProgram() {
+ // skip version byte
+ return convertBits(bytes, 1, bytes.length - 1, 5, 8, false);
+ }
+
+ public byte[] toScriptPubKey() {
+ ByteArrayOutputStream pubkey = new ByteArrayOutputStream(40 + 1);
+ int version = witnessVersion();
+ // OP_0 is encoded as 0x00, but OP_1 through OP_16 are encoded as 0x51 though 0x60
+ if (version > 0)
+ version += 0x50;
+ pubkey.write(version);
+ byte[] program = witnessProgram();
+ pubkey.write(program.length);
+ pubkey.write(program, 0, program.length);
+ return pubkey.toByteArray();
+ }
+
+ @Override
+ public ScriptType getScriptType() {
+ int version = witnessVersion();
+ if (version != 0)
+ return ScriptType.NO_TYPE;
+ int programLength = witnessProgram().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;
+ }
+
+ @Override
+ public String toString() {
+ return toBech32();
+ }
+
+ public static SegwitAddress fromBech32(@Nullable NetworkParameters params, String bech32)
+ throws AddressFormatException {
+ Bech32.Bech32Data data = Bech32.decode(bech32);
+ if (params == null) {
+ for (NetworkParameters p : Networks.get()) {
+ if (data.hrp.equals(p.getSegwitAddressHrp()))
+ return new SegwitAddress(p, data.values);
+ }
+ throw new AddressFormatException("No network found for " + bech32);
+ } else {
+ if (data.hrp.equals(params.getSegwitAddressHrp()))
+ return new SegwitAddress(params, data.values);
+ throw new WrongNetworkException(0 /* dec.hrp */);
+ }
+ }
+
+ public String toBech32() {
+ return Bech32.encode(params.getSegwitAddressHrp(), bytes);
+ }
+
+ 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 510667762105..ec97ccd3bd2a 100644
--- a/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java
+++ b/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java
@@ -56,15 +56,8 @@ public final NetworkParameters getParameters() {
* 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();
+ protected static String toBase58(int version, byte[] bytes) {
+ return Base58.encodeChecked(version, bytes);
}
@Override
diff --git a/core/src/main/java/org/bitcoinj/core/WrongNetworkException.java b/core/src/main/java/org/bitcoinj/core/WrongNetworkException.java
index 3a545cac49f0..1f2e486d0bbd 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 2c5ab80474a4..96da14fa405e 100644
--- a/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java
+++ b/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java
@@ -105,9 +105,11 @@ private BIP38PrivateKey(NetworkParameters params, byte[] bytes, boolean ecMultip
this.content = content;
}
- @Override
- protected int getVersion() {
- return 1;
+ /**
+ * Returns the base58-encoded textual representation, including version and checksum bytes.
+ */
+ public String toBase58() {
+ return toBase58(1, bytes);
}
public ECKey decrypt(String passphrase) throws BadPassphraseException {
@@ -186,4 +188,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 194ff7738cd0..738c50df589c 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 4b1755e3ccaa..cf1ee44089b3 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 e9a3472c7d19..051f6002dc29 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 aa8742f74738..6b19e05e07b3 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)}.
diff --git a/core/src/test/java/org/bitcoinj/core/AddressTest.java b/core/src/test/java/org/bitcoinj/core/AddressTest.java
index 7aadf67ff0dd..32be4913688a 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 000000000000..5b7f1e165bbf
--- /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 dec = Bech32.decode(valid);
+ String recode = Bech32.encode(dec);
+ 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(dec.hrp.toUpperCase(Locale.ROOT), dec.values);
+ 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 000000000000..4356e933a754
--- /dev/null
+++ b/core/src/test/java/org/bitcoinj/core/SegwitAddressTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.junit.Test;
+
+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(address.toScriptPubKey()));
+ assertEquals(ScriptType.P2WPKH, address.getScriptType());
+ 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(address.toScriptPubKey()));
+ assertEquals(ScriptType.P2WSH, address.getScriptType());
+ 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(address.toScriptPubKey()));
+ assertEquals(ScriptType.P2WPKH, address.getScriptType());
+ 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(address.toScriptPubKey()));
+ assertEquals(ScriptType.P2WSH, address.getScriptType());
+ 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.scriptPubKey, Utils.HEX.encode(address.toScriptPubKey()));
+ assertEquals(valid.address.toLowerCase(Locale.ROOT), address.toBech32());
+ }
+ }
+
+ private static class AddressData {
+ final String address;
+ final String scriptPubKey;
+
+ AddressData(String address, String scriptPubKey) {
+ this.address = address;
+ this.scriptPubKey = scriptPubKey;
+ }
+ }
+
+ private static AddressData[] VALID_ADDRESSES = {
+ new AddressData("BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
+ "0014751e76e8199196d454941c45d1b3a323f1433bd6"),
+ new AddressData("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
+ "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"),
+ new AddressData("bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
+ "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"),
+ new AddressData("BC1SW50QA3JX3S", "6002751e"),
+ new AddressData("bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "5210751e76e8199196d454941c45d1b3a323"),
+ new AddressData("tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
+ "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433") };
+
+ @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 96057b6c1d75..9c90e37fae05 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 toBase58(params.getAddressHeader(), bytes);
}
}