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 00000000..d8a5560e --- /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 6e6a8870..170dd35b 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 javax.annotation.Nullable; 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 @@ import com.google.common.base.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. */ -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,16 +168,46 @@ public class Address extends VersionedChecksummedBytes { 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(); } + /** + * Returns the base58-encoded textual form, including version and checksum bytes. + * + * @return textual form + */ + public String toBase58() { + return Base58.encodeChecked(getVersion(), bytes); + } + /** The (big endian) 20 byte hash that is the core of a Bitcoin address. */ 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 @@ -216,6 +247,11 @@ public class Address extends VersionedChecksummedBytes { 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 00000000..e025a872 --- /dev/null +++ b/core/src/main/java/org/bitcoinj/core/Bech32.java @@ -0,0 +1,147 @@ +/* + * Copyright 2018 Coinomi Ltd + * + * 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.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 25a67260..77bc241c 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 @@ public class DumpedPrivateKey extends VersionedChecksummedBytes { 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 class DumpedPrivateKey extends VersionedChecksummedBytes { 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 10eb323f..ca2810f4 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 abstract class NetworkParameters { 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 00000000..92368e89 --- /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: + *
+ * 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 39738273..3b0f8a76 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 abstract class VersionedChecksummedBytes implements Serializable, Cloneab 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 a875ba4f..22b60bb2 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 @@ package org.bitcoinj.core; * 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 b4dc55b3..1f942a3e 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 @@ public class BIP38PrivateKey extends VersionedChecksummedBytes { 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 @@ public class BIP38PrivateKey extends VersionedChecksummedBytes { 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 194ff773..738c50df 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 class MainNetParams extends AbstractBitcoinNetParams { 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 4b1755e3..cf1ee440 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 class TestNet2Params extends AbstractBitcoinNetParams { 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 e9a3472c..051f6002 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 class TestNet3Params extends AbstractBitcoinNetParams { 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 d08e3160..a32b72a9 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 class Script { NO_TYPE, P2PKH, PUB_KEY, - P2SH + P2SH, + P2WPKH, + P2WSH } /** Flags to pass to {@link Script#correctlySpends(Transaction, long, Script, Set)}. @@ -507,8 +509,9 @@ public class Script { 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 @@ public class Script { 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 efca88cb..e5138ce3 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 class ScriptBuilder { } /** Creates a scriptPubKey that encodes payment to the given address. */ - public static Script createOutputScript(Address to) { - if (to.isP2SHAddress()) { - // OP_HASH160