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:
Andreas Schildbach 2018-02-15 14:02:53 +01:00
parent 57f25d4c22
commit 6593d74619
18 changed files with 850 additions and 51 deletions

View 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();
}

View file

@ -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();

View 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));
}
}

View file

@ -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();
}
}

View file

@ -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

View 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();
}
}

View file

@ -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));

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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".

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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. */

View file

@ -104,7 +104,6 @@ public class AddressTest {
fail();
} catch (WrongNetworkException e) {
// Success.
assertEquals(e.verCode, MAINNET.getAddressHeader());
} catch (AddressFormatException e) {
fail();
}

View 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 }),
};
}

View 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);
}
}

View file

@ -35,8 +35,8 @@ public class VersionedChecksummedBytesTest {
}
@Override
protected int getVersion() {
return params.getAddressHeader();
public String toString() {
return Base58.encodeChecked(params.getAddressHeader(), bytes);
}
}