mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-02-24 14:50:57 +01:00
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.
This commit is contained in:
parent
57f25d4c22
commit
6593d74619
18 changed files with 850 additions and 51 deletions
83
core/src/main/java/org/bitcoinj/core/AbstractAddress.java
Normal file
83
core/src/main/java/org/bitcoinj/core/AbstractAddress.java
Normal file
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Base class for addresses, e.g. native segwit addresses ({@link SegwitAddress}) or legacy addresses ({@link Address}).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Use {@link #fromString(NetworkParameters, String)} to conveniently construct any kind of address from its textual
|
||||
* form.
|
||||
* </p>
|
||||
*/
|
||||
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();
|
||||
}
|
|
@ -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.</p>
|
||||
*/
|
||||
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();
|
||||
|
|
147
core/src/main/java/org/bitcoinj/core/Bech32.java
Normal file
147
core/src/main/java/org/bitcoinj/core/Bech32.java
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
255
core/src/main/java/org/bitcoinj/core/SegwitAddress.java
Normal file
255
core/src/main/java/org/bitcoinj/core/SegwitAddress.java
Normal file
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Implementation of native segwit addresses. They are composed of two parts:
|
||||
* <ul>
|
||||
* <li>A human-readable part (HRP) which is a string the specifies the network. See
|
||||
* {@link NetworkParameters#getSegwitAddressHrp()}.</li>
|
||||
* <li>A data part, containing the witness version (encoded as an OP_N operator) and program (encoded by re-arranging
|
||||
* bits into groups of 5).</li>
|
||||
* </ul>
|
||||
* See <a href="https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki">BIP173</a> for details.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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".
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 <scriptHash> 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 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
|
||||
builder.op(OP_DUP);
|
||||
builder.op(OP_HASH160);
|
||||
builder.data(toLegacy.getHash160());
|
||||
builder.op(OP_EQUALVERIFY);
|
||||
builder.op(OP_CHECKSIG);
|
||||
} else if (scriptType == ScriptType.P2SH) {
|
||||
// OP_HASH160 <scriptHash> OP_EQUAL
|
||||
builder.op(OP_HASH160);
|
||||
builder.data(toLegacy.getHash160());
|
||||
builder.op(OP_EQUAL);
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot handle " + scriptType);
|
||||
}
|
||||
} else if (to instanceof SegwitAddress) {
|
||||
// OP_0 <pubKeyHash|scriptHash>
|
||||
SegwitAddress toSegwit = (SegwitAddress) to;
|
||||
builder.smallNum(toSegwit.getWitnessVersion());
|
||||
builder.data(toSegwit.getWitnessProgram());
|
||||
} else {
|
||||
// OP_DUP OP_HASH160 <pubKeyHash> 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. */
|
||||
|
|
|
@ -104,7 +104,6 @@ public class AddressTest {
|
|||
fail();
|
||||
} catch (WrongNetworkException e) {
|
||||
// Success.
|
||||
assertEquals(e.verCode, MAINNET.getAddressHeader());
|
||||
} catch (AddressFormatException e) {
|
||||
fail();
|
||||
}
|
||||
|
|
73
core/src/test/java/org/bitcoinj/core/Bech32Test.java
Normal file
73
core/src/test/java/org/bitcoinj/core/Bech32Test.java
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 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 }),
|
||||
};
|
||||
}
|
175
core/src/test/java/org/bitcoinj/core/SegwitAddressTest.java
Normal file
175
core/src/test/java/org/bitcoinj/core/SegwitAddressTest.java
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -35,8 +35,8 @@ public class VersionedChecksummedBytesTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected int getVersion() {
|
||||
return params.getAddressHeader();
|
||||
public String toString() {
|
||||
return Base58.encodeChecked(params.getAddressHeader(), bytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue