Receive to and send from native segwit addresses

- Hierarchical-deterministic derivation of native segwit addresses.
- Receive payments to native segwit addresses.
- Spend and sign payments from native segwit addresses.
- Watch-only wallets with native segwit addresses (zpub/vpub).
- WalletAppKit, Wallet-tool and Wallet-template are taught to deal with segwit-enabled wallets.

Be aware this adds a new field in the wallet protobuf: output_script_type in Key, which keeps track
of the script type of DeterministicKeyChains. Protobufs will be migrated; old DeterministicKeyChains
are assumed to be of type P2PKH.

Includes some code by Fabrice Drouin.
This commit is contained in:
Andreas Schildbach 2018-04-21 09:57:02 +02:00
parent 691a3b1de8
commit bfe2a195b6
58 changed files with 1776 additions and 349 deletions

View file

@ -19,6 +19,8 @@
package org.bitcoinj.core;
import org.bitcoinj.crypto.*;
import org.bitcoinj.script.Script;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
@ -1288,10 +1290,15 @@ public class ECKey implements EncryptableItem {
}
public void formatKeyWithAddress(boolean includePrivateKeys, @Nullable KeyParameter aesKey, StringBuilder builder,
NetworkParameters params) {
final Address address = LegacyAddress.fromKey(params, this);
NetworkParameters params, Script.ScriptType outputScriptType) {
builder.append(" addr:");
builder.append(address.toString());
if (outputScriptType != null) {
builder.append(Address.fromKey(params, this, outputScriptType));
} else {
builder.append(LegacyAddress.fromKey(params, this));
if (isCompressed())
builder.append(',').append(SegwitAddress.fromKey(params, this));
}
if (!isCompressed())
builder.append(" UNCOMPRESSED");
builder.append(" hash160:");

View file

@ -81,8 +81,10 @@ public abstract class NetworkParameters {
protected int interval;
protected int targetTimespan;
protected byte[] alertSigningKey;
protected int bip32HeaderPub;
protected int bip32HeaderPriv;
protected int bip32HeaderP2PKHpub;
protected int bip32HeaderP2PKHpriv;
protected int bip32HeaderP2WPKHpub;
protected int bip32HeaderP2WPKHpriv;
/** Used to check majorities for block version upgrade */
protected int majorityEnforceBlockUpgrade;
@ -370,16 +372,25 @@ public abstract class NetworkParameters {
return alertSigningKey;
}
/** Returns the 4 byte header for BIP32 (HD) wallet - public key part. */
public int getBip32HeaderPub() {
return bip32HeaderPub;
/** Returns the 4 byte header for BIP32 wallet P2PKH - public key part. */
public int getBip32HeaderP2PKHpub() {
return bip32HeaderP2PKHpub;
}
/** Returns the 4 byte header for BIP32 (HD) wallet - private key part. */
public int getBip32HeaderPriv() {
return bip32HeaderPriv;
/** Returns the 4 byte header for BIP32 wallet P2PKH - private key part. */
public int getBip32HeaderP2PKHpriv() {
return bip32HeaderP2PKHpriv;
}
/** Returns the 4 byte header for BIP32 wallet P2WPKH - public key part. */
public int getBip32HeaderP2WPKHpub() {
return bip32HeaderP2WPKHpub;
}
/** Returns the 4 byte header for BIP32 wallet P2WPKH - private key part. */
public int getBip32HeaderP2WPKHpriv() {
return bip32HeaderP2WPKHpriv;
}
/**
* Returns the number of coins that will be produced in total, on this
* network. Where not applicable, a very large number of coins is returned

View file

@ -921,17 +921,29 @@ public class Transaction extends ChildMessage {
SigHash sigHash, boolean anyoneCanPay) throws ScriptException {
// Verify the API user didn't try to do operations out of order.
checkState(!outputs.isEmpty(), "Attempting to sign tx without outputs.");
TransactionInput input = new TransactionInput(params, this, new byte[]{}, prevOut);
TransactionInput input = new TransactionInput(params, this, new byte[] {}, prevOut);
addInput(input);
Sha256Hash hash = hashForSignature(inputs.size() - 1, scriptPubKey, sigHash, anyoneCanPay);
ECKey.ECDSASignature ecSig = sigKey.sign(hash);
TransactionSignature txSig = new TransactionSignature(ecSig, sigHash, anyoneCanPay);
if (ScriptPattern.isPayToPubKey(scriptPubKey))
input.setScriptSig(ScriptBuilder.createInputScript(txSig));
else if (ScriptPattern.isPayToPubKeyHash(scriptPubKey))
input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey));
else
int inputIndex = inputs.size() - 1;
if (ScriptPattern.isPayToPubKey(scriptPubKey)) {
TransactionSignature signature = calculateSignature(inputIndex, sigKey, scriptPubKey, sigHash,
anyoneCanPay);
input.setScriptSig(ScriptBuilder.createInputScript(signature));
input.setWitness(null);
} else if (ScriptPattern.isPayToPubKeyHash(scriptPubKey)) {
TransactionSignature signature = calculateSignature(inputIndex, sigKey, scriptPubKey, sigHash,
anyoneCanPay);
input.setScriptSig(ScriptBuilder.createInputScript(signature, sigKey));
input.setWitness(null);
} else if (ScriptPattern.isPayToWitnessPubKeyHash(scriptPubKey)) {
Script scriptCode = new ScriptBuilder()
.data(ScriptBuilder.createOutputScript(LegacyAddress.fromKey(params, sigKey)).getProgram()).build();
TransactionSignature signature = calculateWitnessSignature(inputIndex, sigKey, scriptCode, input.getValue(),
sigHash, anyoneCanPay);
input.setScriptSig(ScriptBuilder.createEmpty());
input.setWitness(TransactionWitness.redeemP2WPKH(signature, sigKey));
} else {
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey);
}
return input;
}

View file

@ -1,5 +1,6 @@
/*
* Copyright 2014 Giannis Dzegoutanis
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +17,8 @@
package org.bitcoinj.core;
import javax.annotation.Nullable;
import org.bitcoinj.script.Script;
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.WalletTransaction;
@ -26,8 +29,13 @@ import java.util.Map;
* This interface is used to abstract the {@link Wallet} and the {@link Transaction}
*/
public interface TransactionBag {
/** Returns true if this wallet contains a public key which hashes to the given hash. */
boolean isPubKeyHashMine(byte[] pubKeyHash);
/**
* Look for a public key which hashes to the given hash and (optionally) is used for a specific script type.
* @param pubKeyHash hash of the public key to look for
* @param scriptType only look for given usage (currently {@link Script.ScriptType#P2PKH} or {@link Script.ScriptType#P2WPKH}) or {@code null} if we don't care
* @return true if hash was found
*/
boolean isPubKeyHashMine(byte[] pubKeyHash, @Nullable Script.ScriptType scriptType);
/** Returns true if this wallet is watching transactions for outputs with the script. */
boolean isWatchedScript(Script script);

View file

@ -474,8 +474,8 @@ public class TransactionInput extends ChildMessage {
throw new VerificationException("This input refers to a different output on the given tx.");
}
Script pubKey = output.getScriptPubKey();
int myIndex = getParentTransaction().getInputs().indexOf(this);
getScriptSig().correctlySpends(getParentTransaction(), getIndex(), pubKey);
getScriptSig().correctlySpends(getParentTransaction(), getIndex(), getWitness(), getValue(), pubKey,
Script.ALL_VERIFY_FLAGS);
}
/**

View file

@ -129,7 +129,7 @@ public class TransactionOutPoint extends ChildMessage {
}
/**
* Returns the ECKey identified in the connected output, for either P2PKH scripts or P2PK scripts.
* Returns the ECKey identified in the connected output, for either P2PKH, P2WPKH or P2PK scripts.
* For P2SH scripts you can use {@link #getConnectedRedeemData(KeyBag)} and then get the
* key from RedeemData.
* If the script form cannot be understood, throws ScriptException.
@ -143,7 +143,10 @@ public class TransactionOutPoint extends ChildMessage {
Script connectedScript = connectedOutput.getScriptPubKey();
if (ScriptPattern.isPayToPubKeyHash(connectedScript)) {
byte[] addressBytes = ScriptPattern.extractHashFromPayToPubKeyHash(connectedScript);
return keyBag.findKeyFromPubKeyHash(addressBytes);
return keyBag.findKeyFromPubKeyHash(addressBytes, Script.ScriptType.P2PKH);
} else if (ScriptPattern.isPayToWitnessPubKeyHash(connectedScript)) {
byte[] addressBytes = ScriptPattern.extractHashFromPayToWitnessHash(connectedScript);
return keyBag.findKeyFromPubKeyHash(addressBytes, Script.ScriptType.P2WPKH);
} else if (ScriptPattern.isPayToPubKey(connectedScript)) {
byte[] pubkeyBytes = ScriptPattern.extractKeyFromPayToPubKey(connectedScript);
return keyBag.findKeyFromPubKey(pubkeyBytes);
@ -153,7 +156,7 @@ public class TransactionOutPoint extends ChildMessage {
}
/**
* Returns the RedeemData identified in the connected output, for either P2PKH scripts, P2PK
* Returns the RedeemData identified in the connected output, for either P2PKH, P2WPKH, P2PK
* or P2SH scripts.
* If the script forms cannot be understood, throws ScriptException.
*
@ -166,7 +169,10 @@ public class TransactionOutPoint extends ChildMessage {
Script connectedScript = connectedOutput.getScriptPubKey();
if (ScriptPattern.isPayToPubKeyHash(connectedScript)) {
byte[] addressBytes = ScriptPattern.extractHashFromPayToPubKeyHash(connectedScript);
return RedeemData.of(keyBag.findKeyFromPubKeyHash(addressBytes), connectedScript);
return RedeemData.of(keyBag.findKeyFromPubKeyHash(addressBytes, Script.ScriptType.P2PKH), connectedScript);
} else if (ScriptPattern.isPayToWitnessPubKeyHash(connectedScript)) {
byte[] addressBytes = ScriptPattern.extractHashFromPayToWitnessHash(connectedScript);
return RedeemData.of(keyBag.findKeyFromPubKeyHash(addressBytes, Script.ScriptType.P2WPKH), connectedScript);
} else if (ScriptPattern.isPayToPubKey(connectedScript)) {
byte[] pubkeyBytes = ScriptPattern.extractKeyFromPayToPubKey(connectedScript);
return RedeemData.of(keyBag.findKeyFromPubKey(pubkeyBytes), connectedScript);

View file

@ -306,7 +306,11 @@ public class TransactionOutput extends ChildMessage {
else if (ScriptPattern.isPayToScriptHash(script))
return transactionBag.isPayToScriptHashMine(ScriptPattern.extractHashFromPayToScriptHash(script));
else if (ScriptPattern.isPayToPubKeyHash(script))
return transactionBag.isPubKeyHashMine(ScriptPattern.extractHashFromPayToPubKeyHash(script));
return transactionBag.isPubKeyHashMine(ScriptPattern.extractHashFromPayToPubKeyHash(script),
Script.ScriptType.P2PKH);
else if (ScriptPattern.isPayToWitnessPubKeyHash(script))
return transactionBag.isPubKeyHashMine(ScriptPattern.extractHashFromPayToWitnessHash(script),
Script.ScriptType.P2WPKH);
else
return false;
} catch (ScriptException e) {
@ -325,7 +329,8 @@ public class TransactionOutput extends ChildMessage {
Script script = getScriptPubKey();
StringBuilder buf = new StringBuilder("TxOut of ");
buf.append(Coin.valueOf(value).toFriendlyString());
if (ScriptPattern.isPayToPubKeyHash(script) || ScriptPattern.isPayToScriptHash(script))
if (ScriptPattern.isPayToPubKeyHash(script) || ScriptPattern.isPayToWitnessPubKeyHash(script)
|| ScriptPattern.isPayToScriptHash(script))
buf.append(" to ").append(script.getToAddress(params));
else if (ScriptPattern.isPayToPubKey(script))
buf.append(" to pubkey ").append(Utils.HEX.encode(ScriptPattern.extractKeyFromPayToPubKey(script)));

View file

@ -14,15 +14,33 @@
package org.bitcoinj.core;
import static com.google.common.base.Preconditions.checkArgument;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
import org.bitcoinj.crypto.TransactionSignature;
public class TransactionWitness {
public static final TransactionWitness EMPTY = new TransactionWitness(0);
/**
* Creates the stack pushes necessary to redeem a P2WPKH output. If given signature is null, an empty push will be
* used as a placeholder.
*/
public static TransactionWitness redeemP2WPKH(@Nullable TransactionSignature signature, ECKey pubKey) {
checkArgument(pubKey.isCompressed(), "only compressed keys allowed");
TransactionWitness witness = new TransactionWitness(2);
witness.setPush(0, signature != null ? signature.encodeToBitcoin() : new byte[0]); // signature
witness.setPush(1, pubKey.getPubKey()); // pubkey
return witness;
}
private final List<byte[]> pushes;
public TransactionWitness(int pushCount) {

View file

@ -37,8 +37,9 @@ public class ChildNumber implements Comparable<ChildNumber> {
public static final int HARDENED_BIT = 0x80000000;
public static final ChildNumber ZERO = new ChildNumber(0);
public static final ChildNumber ONE = new ChildNumber(1);
public static final ChildNumber ZERO_HARDENED = new ChildNumber(0, true);
public static final ChildNumber ONE = new ChildNumber(1);
public static final ChildNumber ONE_HARDENED = new ChildNumber(1, true);
/** Integer i as per BIP 32 spec, including the MSB denoting derivation type (0 = public, 1 = private) **/
private final int i;

View file

@ -18,6 +18,7 @@
package org.bitcoinj.crypto;
import org.bitcoinj.core.*;
import org.bitcoinj.script.Script;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
@ -460,17 +461,24 @@ public class DeterministicKey extends ECKey {
return key;
}
@Deprecated
public byte[] serializePublic(NetworkParameters params) {
return serialize(params, true);
return serialize(params, true, Script.ScriptType.P2PKH);
}
@Deprecated
public byte[] serializePrivate(NetworkParameters params) {
return serialize(params, false);
return serialize(params, false, Script.ScriptType.P2PKH);
}
private byte[] serialize(NetworkParameters params, boolean pub) {
private byte[] serialize(NetworkParameters params, boolean pub, Script.ScriptType outputScriptType) {
ByteBuffer ser = ByteBuffer.allocate(78);
ser.putInt(pub ? params.getBip32HeaderPub() : params.getBip32HeaderPriv());
if (outputScriptType == Script.ScriptType.P2PKH)
ser.putInt(pub ? params.getBip32HeaderP2PKHpub() : params.getBip32HeaderP2PKHpriv());
else if (outputScriptType == Script.ScriptType.P2WPKH)
ser.putInt(pub ? params.getBip32HeaderP2WPKHpub() : params.getBip32HeaderP2WPKHpriv());
else
throw new IllegalStateException(outputScriptType.toString());
ser.put((byte) getDepth());
ser.putInt(getParentFingerprint());
ser.putInt(getChildNumber().i());
@ -480,12 +488,20 @@ public class DeterministicKey extends ECKey {
return ser.array();
}
public String serializePubB58(NetworkParameters params, Script.ScriptType outputScriptType) {
return toBase58(serialize(params, true, outputScriptType));
}
public String serializePrivB58(NetworkParameters params, Script.ScriptType outputScriptType) {
return toBase58(serialize(params, false, outputScriptType));
}
public String serializePubB58(NetworkParameters params) {
return toBase58(serialize(params, true));
return serializePubB58(params, Script.ScriptType.P2PKH);
}
public String serializePrivB58(NetworkParameters params) {
return toBase58(serialize(params, false));
return serializePrivB58(params, Script.ScriptType.P2PKH);
}
static String toBase58(byte[] ser) {
@ -520,9 +536,10 @@ public class DeterministicKey extends ECKey {
public static DeterministicKey deserialize(NetworkParameters params, byte[] serializedKey, @Nullable DeterministicKey parent) {
ByteBuffer buffer = ByteBuffer.wrap(serializedKey);
int header = buffer.getInt();
if (header != params.getBip32HeaderPriv() && header != params.getBip32HeaderPub())
final boolean pub = header == params.getBip32HeaderP2PKHpub() || header == params.getBip32HeaderP2WPKHpub();
final boolean priv = header == params.getBip32HeaderP2PKHpriv() || header == params.getBip32HeaderP2WPKHpriv();
if (!(pub || priv))
throw new IllegalArgumentException("Unknown header bytes: " + toBase58(serializedKey).substring(0, 4));
boolean pub = header == params.getBip32HeaderPub();
int depth = buffer.get() & 0xFF; // convert signed byte to positive int since depth cannot be negative
final int parentFingerprint = buffer.getInt();
final int i = buffer.getInt();
@ -615,9 +632,8 @@ public class DeterministicKey extends ECKey {
@Override
public void formatKeyWithAddress(boolean includePrivateKeys, @Nullable KeyParameter aesKey, StringBuilder builder,
NetworkParameters params) {
final Address address = LegacyAddress.fromKey(params, this);
builder.append(" addr:").append(address);
NetworkParameters params, Script.ScriptType outputScriptType) {
builder.append(" addr:").append(Address.fromKey(params, this, outputScriptType).toString());
builder.append(" hash160:").append(Utils.HEX.encode(getPubKeyHash()));
builder.append(" (").append(getPathAsString()).append(")\n");
if (includePrivateKeys) {

View file

@ -25,6 +25,7 @@ import org.bitcoinj.core.*;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.net.discovery.*;
import org.bitcoinj.protocols.channels.*;
import org.bitcoinj.script.Script;
import org.bitcoinj.store.*;
import org.bitcoinj.wallet.*;
import org.slf4j.*;
@ -63,8 +64,10 @@ import static com.google.common.base.Preconditions.*;
public class WalletAppKit extends AbstractIdleService {
protected static final Logger log = LoggerFactory.getLogger(WalletAppKit.class);
protected final String filePrefix;
protected final NetworkParameters params;
protected final Script.ScriptType preferredOutputScriptType;
protected final KeyChainGroupStructure structure;
protected final String filePrefix;
protected volatile BlockChain vChain;
protected volatile BlockStore vStore;
protected volatile Wallet vWallet;
@ -91,15 +94,26 @@ public class WalletAppKit extends AbstractIdleService {
* Creates a new WalletAppKit, with a newly created {@link Context}. Files will be stored in the given directory.
*/
public WalletAppKit(NetworkParameters params, File directory, String filePrefix) {
this(new Context(params), directory, filePrefix);
this(new Context(params), Script.ScriptType.P2PKH, null, directory, filePrefix);
}
/**
* Creates a new WalletAppKit, with a newly created {@link Context}. Files will be stored in the given directory.
*/
public WalletAppKit(NetworkParameters params, Script.ScriptType preferredOutputScriptType,
@Nullable KeyChainGroupStructure structure, File directory, String filePrefix) {
this(new Context(params), preferredOutputScriptType, structure, directory, filePrefix);
}
/**
* Creates a new WalletAppKit, with the given {@link Context}. Files will be stored in the given directory.
*/
public WalletAppKit(Context context, File directory, String filePrefix) {
public WalletAppKit(Context context, Script.ScriptType preferredOutputScriptType,
@Nullable KeyChainGroupStructure structure, File directory, String filePrefix) {
this.context = context;
this.params = checkNotNull(context.getParams());
this.preferredOutputScriptType = checkNotNull(preferredOutputScriptType);
this.structure = structure != null ? structure : KeyChainGroupStructure.DEFAULT;
this.directory = checkNotNull(directory);
this.filePrefix = checkNotNull(filePrefix);
}
@ -434,13 +448,13 @@ public class WalletAppKit extends AbstractIdleService {
}
protected Wallet createWallet() {
KeyChainGroup.Builder kcg = KeyChainGroup.builder(params);
KeyChainGroup.Builder kcg = KeyChainGroup.builder(params, structure);
if (restoreFromSeed != null)
kcg.addChain(DeterministicKeyChain.builder().seed(restoreFromSeed).build());
kcg.addChain(DeterministicKeyChain.builder().seed(restoreFromSeed).outputScriptType(preferredOutputScriptType).build());
else if (restoreFromKey != null)
kcg.addChain(DeterministicKeyChain.builder().spend(restoreFromKey).build());
kcg.addChain(DeterministicKeyChain.builder().spend(restoreFromKey).outputScriptType(preferredOutputScriptType).build());
else
kcg.fromRandom();
kcg.fromRandom(preferredOutputScriptType);
if (walletFactory != null) {
return walletFactory.create(params, kcg.build());
} else {

View file

@ -43,8 +43,10 @@ public class MainNetParams extends AbstractBitcoinNetParams {
segwitAddressHrp = "bc";
port = 8333;
packetMagic = 0xf9beb4d9L;
bip32HeaderPub = 0x0488B21E; //The 4 byte header that serializes in base58 to "xpub".
bip32HeaderPriv = 0x0488ADE4; //The 4 byte header that serializes in base58 to "xprv"
bip32HeaderP2PKHpub = 0x0488b21e; // The 4 byte header that serializes in base58 to "xpub".
bip32HeaderP2PKHpriv = 0x0488ade4; // The 4 byte header that serializes in base58 to "xprv"
bip32HeaderP2WPKHpub = 0x04b24746; // The 4 byte header that serializes in base58 to "zpub".
bip32HeaderP2WPKHpriv = 0x04b2430c; // The 4 byte header that serializes in base58 to "zprv"
majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED;

View file

@ -45,8 +45,10 @@ public class RegTestParams extends AbstractBitcoinNetParams {
checkState(genesisHash.equals("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"));
dnsSeeds = null;
addrSeeds = null;
bip32HeaderPub = 0x043587CF;
bip32HeaderPriv = 0x04358394;
bip32HeaderP2PKHpub = 0x043587cf; // The 4 byte header that serializes in base58 to "tpub".
bip32HeaderP2PKHpriv = 0x04358394; // The 4 byte header that serializes in base58 to "tprv"
bip32HeaderP2WPKHpub = 0x045f1cf6; // The 4 byte header that serializes in base58 to "vpub".
bip32HeaderP2WPKHpriv = 0x045f18bc; // The 4 byte header that serializes in base58 to "vprv"
// Difficulty adjustments are disabled for regtest.
// By setting the block interval for difficulty adjustments to Integer.MAX_VALUE we make sure difficulty never

View file

@ -68,8 +68,10 @@ public class TestNet3Params extends AbstractBitcoinNetParams {
"bitcoin-testnet.bloqseeds.net", // Bloq
};
addrSeeds = null;
bip32HeaderPub = 0x043587CF;
bip32HeaderPriv = 0x04358394;
bip32HeaderP2PKHpub = 0x043587cf; // The 4 byte header that serializes in base58 to "tpub".
bip32HeaderP2PKHpriv = 0x04358394; // The 4 byte header that serializes in base58 to "tprv"
bip32HeaderP2WPKHpub = 0x045f1cf6; // The 4 byte header that serializes in base58 to "vpub".
bip32HeaderP2WPKHpriv = 0x045f18bc; // The 4 byte header that serializes in base58 to "vprv"
majorityEnforceBlockUpgrade = TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED;

View file

@ -1,5 +1,6 @@
/*
* Copyright 2013 Google Inc.
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -47,8 +48,10 @@ public class UnitTestParams extends AbstractBitcoinNetParams {
subsidyDecreaseBlockCount = 100;
dnsSeeds = null;
addrSeeds = null;
bip32HeaderPub = 0x043587CF;
bip32HeaderPriv = 0x04358394;
bip32HeaderP2PKHpub = 0x043587cf; // The 4 byte header that serializes in base58 to "tpub".
bip32HeaderP2PKHpriv = 0x04358394; // The 4 byte header that serializes in base58 to "tprv"
bip32HeaderP2WPKHpub = 0x045f1cf6; // The 4 byte header that serializes in base58 to "vpub".
bip32HeaderP2WPKHpriv = 0x045f18bc; // The 4 byte header that serializes in base58 to "vprv"
majorityEnforceBlockUpgrade = 3;
majorityRejectBlockOutdated = 4;

View file

@ -387,6 +387,8 @@ public class Script {
if (ScriptPattern.isPayToPubKeyHash(this)) {
checkArgument(key != null, "Key required to create P2PKH input script");
return ScriptBuilder.createInputScript(null, key);
} else if (ScriptPattern.isPayToWitnessPubKeyHash(this)) {
return ScriptBuilder.createEmpty();
} else if (ScriptPattern.isPayToPubKey(this)) {
return ScriptBuilder.createInputScript(null);
} else if (ScriptPattern.isPayToScriptHash(this)) {
@ -397,6 +399,18 @@ public class Script {
}
}
public TransactionWitness createEmptyWitness(ECKey key) {
if (ScriptPattern.isPayToWitnessPubKeyHash(this)) {
checkArgument(key != null, "Key required to create P2WPKH witness");
return TransactionWitness.EMPTY;
} else if (ScriptPattern.isPayToPubKey(this) || ScriptPattern.isPayToPubKeyHash(this)
|| ScriptPattern.isPayToScriptHash(this)) {
return null; // no witness
} else {
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Do not understand script type: " + this);
}
}
/**
* Returns a copy of the given scriptSig with the signature inserted in the given position.
*/
@ -607,6 +621,9 @@ public class Script {
// scriptSig: <sig> <pubkey>
int uncompressedPubKeySize = 65;
return SIG_SIZE + (pubKey != null ? pubKey.getPubKey().length : uncompressedPubKeySize);
} else if (ScriptPattern.isPayToWitnessPubKeyHash(this)) {
// scriptSig is empty
return 0;
} else {
throw new IllegalStateException("Unsupported script type");
}
@ -744,7 +761,7 @@ public class Script {
/**
* Exposes the script interpreter. Normally you should not use this directly, instead use
* {@link TransactionInput#verify(TransactionOutput)} or
* {@link Script#correctlySpends(Transaction, long, Script)}. This method
* {@link Script#correctlySpends(Transaction, int, TransactionWitness, Coin, Script, Set)}. This method
* is useful if you need more precise control or access to the final state of the stack. This interface is very
* likely to change in future.
*
@ -764,7 +781,7 @@ public class Script {
/**
* Exposes the script interpreter. Normally you should not use this directly, instead use
* {@link TransactionInput#verify(TransactionOutput)} or
* {@link Script#correctlySpends(Transaction, long, Script)}. This method
* {@link Script#correctlySpends(Transaction, int, TransactionWitness, Coin, Script, Set)}. This method
* is useful if you need more precise control or access to the final state of the stack. This interface is very
* likely to change in future.
*/
@ -1526,7 +1543,7 @@ public class Script {
* Accessing txContainingThis from another thread while this method runs results in undefined behavior.
* @param scriptSigIndex The index in txContainingThis of the scriptSig (note: NOT the index of the scriptPubKey).
* @param scriptPubKey The connected scriptPubKey containing the conditions needed to claim the value.
* @deprecated Use {@link #correctlySpends(Transaction, long, Script, Set)}
* @deprecated Use {@link #correctlySpends(Transaction, int, TransactionWitness, Coin, Script, Set)}
* instead so that verification flags do not change as new verification options
* are added.
*/
@ -1542,8 +1559,43 @@ public class Script {
* Accessing txContainingThis from another thread while this method runs results in undefined behavior.
* @param scriptSigIndex The index in txContainingThis of the scriptSig (note: NOT the index of the scriptPubKey).
* @param scriptPubKey The connected scriptPubKey containing the conditions needed to claim the value.
* @param verifyFlags Each flag enables one validation rule. If in doubt, use {@link #correctlySpends(Transaction, long, Script)}
* which sets all flags.
* @param witness Transaction witness belonging to the transaction input containing this script. Needed for SegWit.
* @param value Value of the output. Needed for SegWit scripts.
* @param verifyFlags Each flag enables one validation rule.
*/
public void correctlySpends(Transaction txContainingThis, int scriptSigIndex, @Nullable TransactionWitness witness, @Nullable Coin value,
Script scriptPubKey, Set<VerifyFlag> verifyFlags) throws ScriptException {
if (ScriptPattern.isPayToWitnessPubKeyHash(scriptPubKey)) {
// For SegWit, full validation isn't implemented. So we simply check the signature. P2SH_P2WPKH is handled
// by the P2SH code for now.
if (witness.getPushCount() < 2)
throw new ScriptException(ScriptError.SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY, witness.toString());
TransactionSignature signature;
try {
signature = TransactionSignature.decodeFromBitcoin(witness.getPush(0), true, true);
} catch (SignatureDecodeException x) {
throw new ScriptException(ScriptError.SCRIPT_ERR_SIG_DER, "Cannot decode", x);
}
ECKey pubkey = ECKey.fromPublicOnly(witness.getPush(1));
Script scriptCode = new ScriptBuilder().data(ScriptBuilder.createP2PKHOutputScript(pubkey).getProgram())
.build();
Sha256Hash sigHash = txContainingThis.hashForSignatureWitness(scriptSigIndex, scriptCode, value,
signature.sigHashMode(), false);
boolean validSig = pubkey.verify(sigHash, signature);
if (!validSig)
throw new ScriptException(ScriptError.SCRIPT_ERR_CHECKSIGVERIFY, "Invalid signature");
} else {
correctlySpends(txContainingThis, scriptSigIndex, scriptPubKey, verifyFlags);
}
}
/**
* Verifies that this script (interpreted as a scriptSig) correctly spends the given scriptPubKey.
* @param txContainingThis The transaction in which this input scriptSig resides.
* Accessing txContainingThis from another thread while this method runs results in undefined behavior.
* @param scriptSigIndex The index in txContainingThis of the scriptSig (note: NOT the index of the scriptPubKey).
* @param scriptPubKey The connected scriptPubKey containing the conditions needed to claim the value.
* @param verifyFlags Each flag enables one validation rule.
*/
public void correctlySpends(Transaction txContainingThis, long scriptSigIndex, Script scriptPubKey,
Set<VerifyFlag> verifyFlags) throws ScriptException {

View file

@ -1,6 +1,7 @@
/*
* Copyright 2013 Google Inc.
* Copyright 2018 Nicola Atzei
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -253,6 +254,11 @@ public class ScriptBuilder {
return new Script(chunks);
}
/** Creates an empty script. */
public static Script createEmpty() {
return new ScriptBuilder().build();
}
/** Creates a scriptPubKey that encodes payment to the given address. */
public static Script createOutputScript(Address to) {
if (to instanceof LegacyAddress) {

View file

@ -1,5 +1,6 @@
/*
* Copyright 2014 Kosta Korenkov
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -70,7 +71,8 @@ public abstract class CustomTransactionSigner implements TransactionSigner {
// We assume if its already signed, its hopefully got a SIGHASH type that will not invalidate when
// we sign missing pieces (to check this would require either assuming any signatures are signing
// standard output types or a way to get processed signatures out of script execution)
txIn.getScriptSig().correctlySpends(tx, i, txIn.getConnectedOutput().getScriptPubKey());
txIn.getScriptSig().correctlySpends(tx, i, txIn.getWitness(), txOut.getValue(), txOut.getScriptPubKey(),
Script.ALL_VERIFY_FLAGS);
log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", i);
continue;
} catch (ScriptException e) {

View file

@ -1,5 +1,6 @@
/*
* Copyright 2014 Kosta Korenkov
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,13 +18,20 @@
package org.bitcoinj.signers;
import java.util.EnumSet;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.TransactionWitness;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptException;
import org.bitcoinj.script.ScriptPattern;
import org.bitcoinj.script.Script.VerifyFlag;
import org.bitcoinj.wallet.KeyBag;
import org.bitcoinj.wallet.RedeemData;
@ -63,16 +71,19 @@ public class LocalTransactionSigner implements TransactionSigner {
int numInputs = tx.getInputs().size();
for (int i = 0; i < numInputs; i++) {
TransactionInput txIn = tx.getInput(i);
if (txIn.getConnectedOutput() == null) {
final TransactionOutput connectedOutput = txIn.getConnectedOutput();
if (connectedOutput == null) {
log.warn("Missing connected output, assuming input {} is already signed.", i);
continue;
}
Script scriptPubKey = connectedOutput.getScriptPubKey();
try {
// We assume if its already signed, its hopefully got a SIGHASH type that will not invalidate when
// we sign missing pieces (to check this would require either assuming any signatures are signing
// standard output types or a way to get processed signatures out of script execution)
txIn.getScriptSig().correctlySpends(tx, i, txIn.getConnectedOutput().getScriptPubKey(), MINIMUM_VERIFY_FLAGS);
txIn.getScriptSig().correctlySpends(tx, i, txIn.getWitness(), connectedOutput.getValue(),
connectedOutput.getScriptPubKey(), MINIMUM_VERIFY_FLAGS);
log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", i);
continue;
} catch (ScriptException e) {
@ -81,8 +92,6 @@ public class LocalTransactionSigner implements TransactionSigner {
RedeemData redeemData = txIn.getConnectedRedeemData(keyBag);
Script scriptPubKey = txIn.getConnectedOutput().getScriptPubKey();
// For P2SH inputs we need to share derivation path of the signing key with other signers, so that they
// use correct key to calculate their signatures.
// Married keys all have the same derivation path, so we can safely just take first one here.
@ -104,18 +113,35 @@ public class LocalTransactionSigner implements TransactionSigner {
// a CHECKMULTISIG program for P2SH inputs
byte[] script = redeemData.redeemScript.getProgram();
try {
TransactionSignature signature = tx.calculateSignature(i, key, script, Transaction.SigHash.ALL, false);
if (ScriptPattern.isPayToPubKey(scriptPubKey) || ScriptPattern.isPayToPubKeyHash(scriptPubKey)
|| ScriptPattern.isPayToScriptHash(scriptPubKey)) {
TransactionSignature signature = tx.calculateSignature(i, key, script, Transaction.SigHash.ALL,
false);
// at this point we have incomplete inputScript with OP_0 in place of one or more signatures. We already
// have calculated the signature using the local key and now need to insert it in the correct place
// within inputScript. For P2PKH and P2PK script there is only one signature and it always
// goes first in an inputScript (sigIndex = 0). In P2SH input scripts we need to figure out our relative
// position relative to other signers. Since we don't have that information at this point, and since
// we always run first, we have to depend on the other signers rearranging the signatures as needed.
// Therefore, always place as first signature.
int sigIndex = 0;
inputScript = scriptPubKey.getScriptSigWithSignature(inputScript, signature.encodeToBitcoin(), sigIndex);
txIn.setScriptSig(inputScript);
// at this point we have incomplete inputScript with OP_0 in place of one or more signatures. We
// already have calculated the signature using the local key and now need to insert it in the
// correct place within inputScript. For P2PKH and P2PK script there is only one signature and it
// always goes first in an inputScript (sigIndex = 0). In P2SH input scripts we need to figure out
// our relative position relative to other signers. Since we don't have that information at this
// point, and since we always run first, we have to depend on the other signers rearranging the
// signatures as needed. Therefore, always place as first signature.
int sigIndex = 0;
inputScript = scriptPubKey.getScriptSigWithSignature(inputScript, signature.encodeToBitcoin(),
sigIndex);
txIn.setScriptSig(inputScript);
txIn.setWitness(null);
} else if (ScriptPattern.isPayToWitnessPubKeyHash(scriptPubKey)) {
Script scriptCode = new ScriptBuilder().data(
ScriptBuilder.createOutputScript(LegacyAddress.fromKey(tx.getParams(), key)).getProgram())
.build();
Coin value = txIn.getValue();
TransactionSignature signature = tx.calculateWitnessSignature(i, key, scriptCode, value,
Transaction.SigHash.ALL, false);
txIn.setScriptSig(ScriptBuilder.createEmpty());
txIn.setWitness(TransactionWitness.redeemP2WPKH(signature, key));
} else {
throw new IllegalStateException(script.toString());
}
} catch (ECKey.KeyIsEncryptedException e) {
throw e;
} catch (ECKey.MissingPrivateKeyException e) {

View file

@ -1,5 +1,6 @@
/*
* Copyright 2014 Kosta Korenkov
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,6 +19,7 @@ package org.bitcoinj.signers;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionWitness;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptChunk;
@ -80,7 +82,7 @@ public class MissingSigResolutionSigner implements TransactionSigner {
}
}
}
} else {
} else if (ScriptPattern.isPayToPubKey(scriptPubKey) || ScriptPattern.isPayToPubKeyHash(scriptPubKey)) {
if (inputScript.getChunks().get(0).equalsOpCode(0)) {
if (missingSigsMode == Wallet.MissingSigsMode.THROW) {
throw new ECKey.MissingPrivateKeyException();
@ -88,8 +90,20 @@ public class MissingSigResolutionSigner implements TransactionSigner {
txIn.setScriptSig(scriptPubKey.getScriptSigWithSignature(inputScript, dummySig, 0));
}
}
} else if (ScriptPattern.isPayToWitnessPubKeyHash(scriptPubKey)) {
if (txIn.getWitness() == null || txIn.getWitness().equals(TransactionWitness.EMPTY)
|| txIn.getWitness().getPush(0).length == 0) {
if (missingSigsMode == Wallet.MissingSigsMode.THROW) {
throw new ECKey.MissingPrivateKeyException();
} else if (missingSigsMode == Wallet.MissingSigsMode.USE_DUMMY_SIG) {
ECKey key = keyBag.findKeyFromPubKeyHash(
ScriptPattern.extractHashFromPayToWitnessHash(scriptPubKey), Script.ScriptType.P2WPKH);
txIn.setWitness(TransactionWitness.redeemP2WPKH(TransactionSignature.dummy(), key));
}
}
} else {
throw new IllegalStateException("cannot handle: " + scriptPubKey);
}
// TODO handle non-P2SH multisig
}
return true;
}

View file

@ -1,5 +1,6 @@
/*
* Copyright 2013 Google Inc.
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -204,19 +205,19 @@ public class BasicKeyChain implements EncryptableKeyChain {
}
}
public ECKey findKeyFromPubHash(byte[] pubkeyHash) {
public ECKey findKeyFromPubHash(byte[] pubKeyHash) {
lock.lock();
try {
return hashToKeys.get(ByteString.copyFrom(pubkeyHash));
return hashToKeys.get(ByteString.copyFrom(pubKeyHash));
} finally {
lock.unlock();
}
}
public ECKey findKeyFromPubKey(byte[] pubkey) {
public ECKey findKeyFromPubKey(byte[] pubKey) {
lock.lock();
try {
return pubkeyToKeys.get(ByteString.copyFrom(pubkey));
return pubkeyToKeys.get(ByteString.copyFrom(pubKey));
} finally {
lock.unlock();
}
@ -625,7 +626,7 @@ public class BasicKeyChain implements EncryptableKeyChain {
List<ECKey> keys = getKeys();
Collections.sort(keys, ECKey.AGE_COMPARATOR);
for (ECKey key : keys)
key.formatKeyWithAddress(includePrivateKeys, aesKey, builder, params);
key.formatKeyWithAddress(includePrivateKeys, aesKey, builder, params, null);
return builder.toString();
}
}

View file

@ -17,6 +17,7 @@
package org.bitcoinj.wallet;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.script.Script;
import org.bouncycastle.crypto.params.KeyParameter;
import javax.annotation.Nullable;
@ -63,8 +64,8 @@ public class DecryptingKeyBag implements KeyBag {
@Nullable
@Override
public ECKey findKeyFromPubKeyHash(byte[] pubKeyHash) {
return maybeDecrypt(target.findKeyFromPubKeyHash(pubKeyHash));
public ECKey findKeyFromPubKeyHash(byte[] pubKeyHash, @Nullable Script.ScriptType scriptType) {
return maybeDecrypt(target.findKeyFromPubKeyHash(pubKeyHash, scriptType));
}
@Nullable

View file

@ -1,5 +1,6 @@
/*
* Copyright 2014 devrandom
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,6 +18,7 @@
package org.bitcoinj.wallet;
import org.bitcoinj.crypto.*;
import org.bitcoinj.script.Script;
import com.google.common.collect.ImmutableList;
@ -26,36 +28,39 @@ import com.google.common.collect.ImmutableList;
public class DefaultKeyChainFactory implements KeyChainFactory {
@Override
public DeterministicKeyChain makeKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicSeed seed,
KeyCrypter crypter, boolean isMarried, ImmutableList<ChildNumber> accountPath) {
KeyCrypter crypter, boolean isMarried, Script.ScriptType outputScriptType,
ImmutableList<ChildNumber> accountPath) {
DeterministicKeyChain chain;
if (isMarried)
chain = new MarriedKeyChain(seed, crypter, accountPath);
chain = new MarriedKeyChain(seed, crypter, outputScriptType, accountPath);
else
chain = new DeterministicKeyChain(seed, crypter, accountPath);
chain = new DeterministicKeyChain(seed, crypter, outputScriptType, accountPath);
return chain;
}
@Override
public DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey,
boolean isFollowingKey, boolean isMarried) throws UnreadableWalletException {
public DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey,
DeterministicKey accountKey, boolean isFollowingKey, boolean isMarried, Script.ScriptType outputScriptType)
throws UnreadableWalletException {
DeterministicKeyChain chain;
if (isMarried)
chain = new MarriedKeyChain(accountKey);
chain = new MarriedKeyChain(accountKey, outputScriptType);
else if (isFollowingKey)
chain = DeterministicKeyChain.builder().watchAndFollow(accountKey).build();
chain = DeterministicKeyChain.builder().watchAndFollow(accountKey).outputScriptType(outputScriptType).build();
else
chain = DeterministicKeyChain.builder().watch(accountKey).build();
chain = DeterministicKeyChain.builder().watch(accountKey).outputScriptType(outputScriptType).build();
return chain;
}
@Override
public DeterministicKeyChain makeSpendingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey,
boolean isMarried) throws UnreadableWalletException {
public DeterministicKeyChain makeSpendingKeyChain(Protos.Key key, Protos.Key firstSubKey,
DeterministicKey accountKey, boolean isMarried, Script.ScriptType outputScriptType)
throws UnreadableWalletException {
DeterministicKeyChain chain;
if (isMarried)
chain = new MarriedKeyChain(accountKey);
chain = new MarriedKeyChain(accountKey, outputScriptType);
else
chain = DeterministicKeyChain.builder().spend(accountKey).build();
chain = DeterministicKeyChain.builder().spend(accountKey).outputScriptType(outputScriptType).build();
return chain;
}
}

View file

@ -105,7 +105,8 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
private DeterministicHierarchy hierarchy;
@Nullable private DeterministicKey rootKey;
@Nullable private DeterministicSeed seed;
@Nullable private final ImmutableList<ChildNumber> accountPath;
private final Script.ScriptType outputScriptType;
private final ImmutableList<ChildNumber> accountPath;
// Paths through the key tree. External keys are ones that are communicated to other parties. Internal keys are
// keys created for change addresses, coinbases, mixing, etc - anything that isn't communicated. The distinction
@ -113,7 +114,10 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
// that feature yet. In future we might hand out different accounts for cases where we wish to hand payers
// a payment request that can generate lots of addresses independently.
// The account path may be overridden by subclasses.
// m / 0'
public static final ImmutableList<ChildNumber> ACCOUNT_ZERO_PATH = ImmutableList.of(ChildNumber.ZERO_HARDENED);
// m / 1'
public static final ImmutableList<ChildNumber> ACCOUNT_ONE_PATH = ImmutableList.of(ChildNumber.ONE_HARDENED);
// m / 44' / 0' / 0'
public static final ImmutableList<ChildNumber> BIP44_ACCOUNT_ZERO_PATH = ImmutableList.of(new ChildNumber(44, true),
ChildNumber.ZERO_HARDENED, ChildNumber.ZERO_HARDENED);
@ -167,6 +171,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
protected long creationTimeSecs = 0;
protected byte[] entropy;
protected DeterministicSeed seed;
protected Script.ScriptType outputScriptType = Script.ScriptType.P2PKH;
protected DeterministicKey watchingKey = null;
protected boolean isFollowing = false;
protected DeterministicKey spendingKey = null;
@ -255,6 +260,11 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
return self();
}
public T outputScriptType(Script.ScriptType outputScriptType) {
this.outputScriptType = outputScriptType;
return self();
}
/** The passphrase to use with the generated mnemonic, or null if you would like to use the default empty string. Currently must be the empty string. */
public T passphrase(String passphrase) {
// FIXME support non-empty passphrase
@ -267,7 +277,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
*/
public T accountPath(ImmutableList<ChildNumber> accountPath) {
checkState(watchingKey == null, "either watch or accountPath");
this.accountPath = accountPath;
this.accountPath = checkNotNull(accountPath);
return self();
}
@ -280,16 +290,16 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
if (random != null)
// Default passphrase to "" if not specified
return new DeterministicKeyChain(new DeterministicSeed(random, bits, getPassphrase()), null,
accountPath);
outputScriptType, accountPath);
else if (entropy != null)
return new DeterministicKeyChain(new DeterministicSeed(entropy, getPassphrase(), creationTimeSecs),
null, accountPath);
null, outputScriptType, accountPath);
else if (seed != null)
return new DeterministicKeyChain(seed, null, accountPath);
return new DeterministicKeyChain(seed, null, outputScriptType, accountPath);
else if (watchingKey != null)
return new DeterministicKeyChain(watchingKey, isFollowing, true);
return new DeterministicKeyChain(watchingKey, isFollowing, true, outputScriptType);
else if (spendingKey != null)
return new DeterministicKeyChain(spendingKey, false, false);
return new DeterministicKeyChain(spendingKey, false, false, outputScriptType);
else
throw new IllegalStateException();
}
@ -318,7 +328,8 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
* {@link Builder}.
* </p>
*/
public DeterministicKeyChain(DeterministicKey key, boolean isFollowing, boolean isWatching) {
public DeterministicKeyChain(DeterministicKey key, boolean isFollowing, boolean isWatching,
Script.ScriptType outputScriptType) {
if (isWatching)
checkArgument(key.isPubKeyOnly(), "Private subtrees not currently supported for watching keys: if you got this key from DKC.getWatchingKey() then use .dropPrivate().dropParent() on it first.");
else
@ -331,6 +342,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
basicKeyChain.importKey(key);
hierarchy = new DeterministicHierarchy(key);
this.accountPath = key.getPath();
this.outputScriptType = outputScriptType;
initializeHierarchyUnencrypted(key);
this.isFollowing = isFollowing;
}
@ -347,7 +359,10 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
* </p>
*/
protected DeterministicKeyChain(DeterministicSeed seed, @Nullable KeyCrypter crypter,
ImmutableList<ChildNumber> accountPath) {
Script.ScriptType outputScriptType, ImmutableList<ChildNumber> accountPath) {
checkArgument(outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH
|| outputScriptType == Script.ScriptType.P2WPKH, "Only P2PKH or P2WPKH allowed.");
this.outputScriptType = outputScriptType != null ? outputScriptType : Script.ScriptType.P2PKH;
this.accountPath = accountPath;
this.seed = seed;
basicKeyChain = new BasicKeyChain(crypter);
@ -370,7 +385,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
* For use in encryption when {@link #toEncrypted(KeyCrypter, KeyParameter)} is called, so that
* subclasses can override that method and create an instance of the right class.
*
* See also {@link #makeKeyChainFromSeed(DeterministicSeed, ImmutableList)}
* See also {@link #makeKeyChainFromSeed(DeterministicSeed, ImmutableList, Script.ScriptType)}
*/
protected DeterministicKeyChain(KeyCrypter crypter, KeyParameter aesKey, DeterministicKeyChain chain) {
// Can't encrypt a watching chain.
@ -379,6 +394,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
checkArgument(!chain.rootKey.isEncrypted(), "Chain already encrypted");
this.accountPath = chain.getAccountPath();
this.outputScriptType = chain.outputScriptType;
this.issuedExternalKeys = chain.issuedExternalKeys;
this.issuedInternalKeys = chain.issuedInternalKeys;
@ -413,11 +429,12 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
}
}
/** Override in subclasses to use a different account derivation path */
protected ImmutableList<ChildNumber> getAccountPath() {
if (accountPath != null)
return accountPath;
return ACCOUNT_ZERO_PATH;
public ImmutableList<ChildNumber> getAccountPath() {
return accountPath;
}
public Script.ScriptType getOutputScriptType() {
return outputScriptType;
}
private DeterministicKey encryptNonLeaf(KeyParameter aesKey, DeterministicKeyChain chain,
@ -755,6 +772,8 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
if (key.getParent() != null) {
// HD keys inherit the timestamp of their parent if they have one, so no need to serialize it.
proto.clearCreationTimestamp();
} else {
proto.setOutputScriptType(Protos.Key.OutputScriptType.valueOf(outputScriptType.name()));
}
entries.add(proto.build());
}
@ -778,6 +797,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
int sigsRequiredToSpend = 1;
List<ChildNumber> accountPath = newArrayList();
Script.ScriptType outputScriptType = Script.ScriptType.P2PKH;
PeekingIterator<Protos.Key> iter = Iterators.peekingIterator(keys.iterator());
while (iter.hasNext()) {
Protos.Key key = iter.next();
@ -835,6 +855,8 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
// Deserialize the public key and path.
LazyECPoint pubkey = new LazyECPoint(ECKey.CURVE.getCurve(), key.getPublicKey().toByteArray());
final ImmutableList<ChildNumber> immutablePath = ImmutableList.copyOf(path);
if (key.hasOutputScriptType())
outputScriptType = Script.ScriptType.valueOf(key.getOutputScriptType().name());
// Possibly create the chain, if we didn't already do so yet.
boolean isWatchingAccountKey = false;
boolean isFollowingKey = false;
@ -860,16 +882,17 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
if (seed == null && key.hasSecretBytes()) {
DeterministicKey accountKey = new DeterministicKey(immutablePath, chainCode, pubkey, new BigInteger(1, key.getSecretBytes().toByteArray()), null);
accountKey.setCreationTimeSeconds(key.getCreationTimestamp() / 1000);
chain = factory.makeSpendingKeyChain(key, iter.peek(), accountKey, isMarried);
chain = factory.makeSpendingKeyChain(key, iter.peek(), accountKey, isMarried, outputScriptType);
isSpendingKey = true;
} else if (seed == null) {
DeterministicKey accountKey = new DeterministicKey(immutablePath, chainCode, pubkey, null, null);
accountKey.setCreationTimeSeconds(key.getCreationTimestamp() / 1000);
chain = factory.makeWatchingKeyChain(key, iter.peek(), accountKey, isFollowingKey, isMarried);
chain = factory.makeWatchingKeyChain(key, iter.peek(), accountKey, isFollowingKey, isMarried,
outputScriptType);
isWatchingAccountKey = true;
} else {
chain = factory.makeKeyChain(key, iter.peek(), seed, crypter, isMarried,
ImmutableList.<ChildNumber> builder().addAll(accountPath).build());
outputScriptType, ImmutableList.<ChildNumber> builder().addAll(accountPath).build());
chain.lookaheadSize = LAZY_CALCULATE_LOOKAHEAD;
// If the seed is encrypted, then the chain is incomplete at this point. However, we will load
// it up below as we parse in the keys. We just need to check at the end that we've loaded
@ -987,7 +1010,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
checkState(seed.isEncrypted());
String passphrase = DEFAULT_PASSPHRASE_FOR_MNEMONIC; // FIXME allow non-empty passphrase
DeterministicSeed decSeed = seed.decrypt(getKeyCrypter(), passphrase, aesKey);
DeterministicKeyChain chain = makeKeyChainFromSeed(decSeed, getAccountPath());
DeterministicKeyChain chain = makeKeyChainFromSeed(decSeed, getAccountPath(), outputScriptType);
// Now double check that the keys match to catch the case where the key is wrong but padding didn't catch it.
if (!chain.getWatchingKey().getPubKeyPoint().equals(getWatchingKey().getPubKeyPoint()))
throw new KeyCrypterException("Provided AES key is wrong");
@ -1014,8 +1037,9 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
* Subclasses should override this to create an instance of the subclass instead of a plain DKC.
* This is used in encryption/decryption.
*/
protected DeterministicKeyChain makeKeyChainFromSeed(DeterministicSeed seed, ImmutableList<ChildNumber> accountPath) {
return new DeterministicKeyChain(seed, null, accountPath);
protected DeterministicKeyChain makeKeyChainFromSeed(DeterministicSeed seed, ImmutableList<ChildNumber> accountPath,
Script.ScriptType outputScriptType) {
return new DeterministicKeyChain(seed, null, outputScriptType, accountPath);
}
@Override
@ -1337,21 +1361,24 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
if (seed != null) {
if (includePrivateKeys) {
DeterministicSeed decryptedSeed = seed.isEncrypted()
? seed.decrypt(getKeyCrypter(), DEFAULT_PASSPHRASE_FOR_MNEMONIC, aesKey) : seed;
? seed.decrypt(getKeyCrypter(), DEFAULT_PASSPHRASE_FOR_MNEMONIC, aesKey)
: seed;
final List<String> words = decryptedSeed.getMnemonicCode();
builder.append("Seed as words: ").append(Utils.SPACE_JOINER.join(words)).append('\n');
builder.append("Seed as hex: ").append(decryptedSeed.toHexString()).append('\n');
builder.append("Seed as words: ").append(Utils.SPACE_JOINER.join(words)).append('\n');
builder.append("Seed as hex: ").append(decryptedSeed.toHexString()).append('\n');
} else {
if (seed.isEncrypted())
builder.append("Seed is encrypted\n");
}
builder.append("Seed birthday: ").append(seed.getCreationTimeSeconds()).append(" [")
builder.append("Seed birthday: ").append(seed.getCreationTimeSeconds()).append(" [")
.append(Utils.dateTimeFormat(seed.getCreationTimeSeconds() * 1000)).append("]\n");
} else {
builder.append("Key birthday: ").append(watchingKey.getCreationTimeSeconds()).append(" [")
builder.append("Key birthday: ").append(watchingKey.getCreationTimeSeconds()).append(" [")
.append(Utils.dateTimeFormat(watchingKey.getCreationTimeSeconds() * 1000)).append("]\n");
}
builder.append("Key to watch: ").append(watchingKey.serializePubB58(params)).append('\n');
builder.append("Ouput script type: ").append(outputScriptType).append('\n');
builder.append("Key to watch: ").append(watchingKey.serializePubB58(params, outputScriptType))
.append('\n');
formatAddresses(includePrivateKeys, aesKey, params, builder);
return builder.toString();
}
@ -1359,7 +1386,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
protected void formatAddresses(boolean includePrivateKeys, @Nullable KeyParameter aesKey, NetworkParameters params,
StringBuilder builder) {
for (ECKey key : getKeys(false, true))
key.formatKeyWithAddress(includePrivateKeys, aesKey, builder, params);
key.formatKeyWithAddress(includePrivateKeys, aesKey, builder, params, outputScriptType);
}
/** The number of signatures required to spend coins received by this keychain. */

View file

@ -17,6 +17,7 @@
package org.bitcoinj.wallet;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.script.Script;
import javax.annotation.Nullable;
@ -26,13 +27,18 @@ import javax.annotation.Nullable;
*/
public interface KeyBag {
/**
* Locates a keypair from the keychain given the hash of the public key. This is needed when finding out which
* key we need to use to redeem a transaction output.
* Locates a keypair from the keychain given the hash of the public key, and (optionally) by usage for a specific
* script type. This is needed when finding out which key we need to use to redeem a transaction output.
*
* @return ECKey object or null if no such key was found.
* @param pubKeyHash
* hash of the keypair to look for
* @param scriptType
* only look for given usage (currently {@link Script.ScriptType#P2PKH} or
* {@link Script.ScriptType#P2WPKH}) or {@code null} if we don't care
* @return found key or null if no such key was found.
*/
@Nullable
ECKey findKeyFromPubKeyHash(byte[] pubKeyHash);
ECKey findKeyFromPubKeyHash(byte[] pubKeyHash, @Nullable Script.ScriptType scriptType);
/**
* Locates a keypair from the keychain given the raw public key bytes.

View file

@ -1,5 +1,6 @@
/*
* Copyright 2014 devrandom
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,6 +21,7 @@ import com.google.common.collect.ImmutableList;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypter;
import org.bitcoinj.script.Script;
/**
* Factory interface for creation keychains while de-serializing a wallet.
@ -33,11 +35,12 @@ public interface KeyChainFactory {
* @param seed the seed
* @param crypter the encrypted/decrypter
* @param isMarried whether the keychain is leading in a marriage
* @param accountPath the specified account path
* @param outputScriptType type of addresses (aka output scripts) to generate for receiving
* @param accountPath account path to generate receiving addresses on
*/
DeterministicKeyChain makeKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicSeed seed,
KeyCrypter crypter, boolean isMarried,
ImmutableList<ChildNumber> accountPath);
KeyCrypter crypter, boolean isMarried, Script.ScriptType outputScriptType,
ImmutableList<ChildNumber> accountPath);
/**
* Make a watching keychain.
@ -49,9 +52,11 @@ public interface KeyChainFactory {
* @param accountKey the account extended public key
* @param isFollowingKey whether the keychain is following in a marriage
* @param isMarried whether the keychain is leading in a marriage
* @param outputScriptType type of addresses (aka output scripts) to generate for watching
*/
DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey,
boolean isFollowingKey, boolean isMarried) throws UnreadableWalletException;
boolean isFollowingKey, boolean isMarried, Script.ScriptType outputScriptType)
throws UnreadableWalletException;
/**
* Make a spending keychain.
@ -62,7 +67,8 @@ public interface KeyChainFactory {
* @param firstSubKey the protobuf for the first child key (normally the parent of the external subchain)
* @param accountKey the account extended public key
* @param isMarried whether the keychain is leading in a marriage
* @param outputScriptType type of addresses (aka output scripts) to generate for spending
*/
DeterministicKeyChain makeSpendingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey,
boolean isMarried) throws UnreadableWalletException;
boolean isMarried, Script.ScriptType outputScriptType) throws UnreadableWalletException;
}

View file

@ -67,18 +67,22 @@ public class KeyChainGroup implements KeyBag {
*/
public static class Builder {
private final NetworkParameters params;
private final KeyChainGroupStructure structure;
private final List<DeterministicKeyChain> chains = new LinkedList<DeterministicKeyChain>();
private Builder(NetworkParameters params) {
private Builder(NetworkParameters params, KeyChainGroupStructure structure) {
this.params = params;
this.structure = structure;
}
/**
* Add chain from a random source.
* @param outputScriptType type of addresses (aka output scripts) to generate for receiving
*/
public Builder fromRandom() {
public Builder fromRandom(Script.ScriptType outputScriptType) {
this.chains.clear();
DeterministicKeyChain chain = DeterministicKeyChain.builder().random(new SecureRandom()).build();
DeterministicKeyChain chain = DeterministicKeyChain.builder().random(new SecureRandom())
.outputScriptType(outputScriptType).accountPath(structure.accountPathFor(outputScriptType)).build();
this.chains.add(chain);
return this;
}
@ -86,10 +90,12 @@ public class KeyChainGroup implements KeyBag {
/**
* Add chain from a given seed.
* @param seed deterministic seed to derive all keys from
* @param outputScriptType type of addresses (aka output scripts) to generate for receiving
*/
public Builder fromSeed(DeterministicSeed seed) {
public Builder fromSeed(DeterministicSeed seed, Script.ScriptType outputScriptType) {
this.chains.clear();
DeterministicKeyChain chain = DeterministicKeyChain.builder().seed(seed).build();
DeterministicKeyChain chain = DeterministicKeyChain.builder().seed(seed).outputScriptType(outputScriptType)
.accountPath(structure.accountPathFor(outputScriptType)).build();
this.chains.add(chain);
return this;
}
@ -144,7 +150,11 @@ public class KeyChainGroup implements KeyBag {
}
public static KeyChainGroup.Builder builder(NetworkParameters params) {
return new Builder(params);
return new Builder(params, KeyChainGroupStructure.DEFAULT);
}
public static KeyChainGroup.Builder builder(NetworkParameters params, KeyChainGroupStructure structure) {
return new Builder(params, structure);
}
private KeyChainGroup(NetworkParameters params, @Nullable BasicKeyChain basicKeyChain, @Nullable List<DeterministicKeyChain> chains,
@ -235,6 +245,7 @@ public class KeyChainGroup implements KeyBag {
*/
public Address currentAddress(KeyChain.KeyPurpose purpose) {
DeterministicKeyChain chain = getActiveKeyChain();
Script.ScriptType outputScriptType = chain.getOutputScriptType();
if (chain.isMarried()) {
Address current = currentAddresses.get(purpose);
if (current == null) {
@ -242,8 +253,10 @@ public class KeyChainGroup implements KeyBag {
currentAddresses.put(purpose, current);
}
return current;
} else if (outputScriptType == Script.ScriptType.P2PKH || outputScriptType == Script.ScriptType.P2WPKH) {
return Address.fromKey(params, currentKey(purpose), outputScriptType);
} else {
return LegacyAddress.fromKey(params, currentKey(purpose));
throw new IllegalStateException(chain.getOutputScriptType().toString());
}
}
@ -289,6 +302,7 @@ public class KeyChainGroup implements KeyBag {
*/
public Address freshAddress(KeyChain.KeyPurpose purpose) {
DeterministicKeyChain chain = getActiveKeyChain();
Script.ScriptType outputScriptType = chain.getOutputScriptType();
if (chain.isMarried()) {
Script outputScript = chain.freshOutputScript(purpose);
checkState(ScriptPattern.isPayToScriptHash(outputScript)); // Only handle P2SH for now
@ -297,8 +311,10 @@ public class KeyChainGroup implements KeyBag {
maybeLookaheadScripts();
currentAddresses.put(purpose, freshAddress);
return freshAddress;
} else if (outputScriptType == Script.ScriptType.P2PKH || outputScriptType == Script.ScriptType.P2WPKH) {
return Address.fromKey(params, freshKey(purpose), outputScriptType);
} else {
return LegacyAddress.fromKey(params, freshKey(purpose));
throw new IllegalStateException(chain.getOutputScriptType().toString());
}
}
@ -432,14 +448,20 @@ public class KeyChainGroup implements KeyBag {
@Nullable
@Override
public ECKey findKeyFromPubKeyHash(byte[] pubKeyHash) {
public ECKey findKeyFromPubKeyHash(byte[] pubKeyHash, @Nullable Script.ScriptType scriptType) {
ECKey result;
// BasicKeyChain can mix output script types.
if ((result = basic.findKeyFromPubHash(pubKeyHash)) != null)
return result;
if (chains != null)
for (DeterministicKeyChain chain : chains)
if (chains != null) {
for (DeterministicKeyChain chain : chains) {
// This check limits DeterministicKeyChain to specific output script usage.
if (scriptType != null && scriptType != chain.getOutputScriptType())
continue;
if ((result = chain.findKeyFromPubHash(pubKeyHash)) != null)
return result;
}
}
return null;
}

View file

@ -0,0 +1,41 @@
/*
* Copyright by the original author or authors.
*
* 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.wallet;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.script.Script;
import com.google.common.collect.ImmutableList;
/** Defines a structure for hierarchical deterministic wallets. */
public interface KeyChainGroupStructure {
/** Map desired output script type to an account path */
ImmutableList<ChildNumber> accountPathFor(Script.ScriptType outputScriptType);
/** Default {@link KeyChainGroupStructure} implementation. Based on BIP32 "Wallet structure". */
public static final KeyChainGroupStructure DEFAULT = new KeyChainGroupStructure() {
@Override
public ImmutableList<ChildNumber> accountPathFor(Script.ScriptType outputScriptType) {
if (outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH)
return DeterministicKeyChain.ACCOUNT_ZERO_PATH;
else if (outputScriptType == Script.ScriptType.P2WPKH)
return DeterministicKeyChain.ACCOUNT_ONE_PATH;
else
throw new IllegalArgumentException(outputScriptType.toString());
}
};
}

View file

@ -1,5 +1,6 @@
/*
* Copyright 2013 Google Inc.
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -65,7 +66,9 @@ public class KeyTimeCoinSelector implements CoinSelector {
if (ScriptPattern.isPayToPubKey(scriptPubKey)) {
controllingKey = wallet.findKeyFromPubKey(ScriptPattern.extractKeyFromPayToPubKey(scriptPubKey));
} else if (ScriptPattern.isPayToPubKeyHash(scriptPubKey)) {
controllingKey = wallet.findKeyFromPubKeyHash(ScriptPattern.extractHashFromPayToPubKeyHash(scriptPubKey));
controllingKey = wallet.findKeyFromPubKeyHash(ScriptPattern.extractHashFromPayToPubKeyHash(scriptPubKey), Script.ScriptType.P2PKH);
} else if (ScriptPattern.isPayToWitnessPubKeyHash(scriptPubKey)) {
controllingKey = wallet.findKeyFromPubKeyHash(ScriptPattern.extractHashFromPayToWitnessHash(scriptPubKey), Script.ScriptType.P2WPKH);
} else {
log.info("Skipping tx output {} because it's not of simple form.", output);
continue;

View file

@ -102,14 +102,14 @@ public class MarriedKeyChain extends DeterministicKeyChain {
MarriedKeyChain chain;
if (random != null)
chain = new MarriedKeyChain(new DeterministicSeed(random, bits, getPassphrase()), null, accountPath);
chain = new MarriedKeyChain(new DeterministicSeed(random, bits, getPassphrase()), null, outputScriptType, accountPath);
else if (entropy != null)
chain = new MarriedKeyChain(new DeterministicSeed(entropy, getPassphrase(), creationTimeSecs), null,
accountPath);
outputScriptType, accountPath);
else if (seed != null)
chain = new MarriedKeyChain(seed, null, accountPath);
chain = new MarriedKeyChain(seed, null, outputScriptType, accountPath);
else if (watchingKey != null)
chain = new MarriedKeyChain(watchingKey);
chain = new MarriedKeyChain(watchingKey, outputScriptType);
else
throw new IllegalStateException();
chain.addFollowingAccountKeys(followingKeys, threshold);
@ -125,16 +125,16 @@ public class MarriedKeyChain extends DeterministicKeyChain {
* This constructor is not stable across releases! If you need a stable API, use {@link #builder()} to use a
* {@link Builder}.
*/
protected MarriedKeyChain(DeterministicKey accountKey) {
super(accountKey, false, true);
protected MarriedKeyChain(DeterministicKey accountKey, Script.ScriptType outputScriptType) {
super(accountKey, false, true, outputScriptType);
}
/**
* This constructor is not stable across releases! If you need a stable API, use {@link #builder()} to use a
* {@link Builder}.
*/
protected MarriedKeyChain(DeterministicSeed seed, KeyCrypter crypter, ImmutableList<ChildNumber> accountPath) {
super(seed, crypter, accountPath);
protected MarriedKeyChain(DeterministicSeed seed, KeyCrypter crypter, Script.ScriptType outputScriptType, ImmutableList<ChildNumber> accountPath) {
super(seed, crypter, outputScriptType, accountPath);
}
void setFollowingKeyChains(List<DeterministicKeyChain> followingKeyChains) {
@ -189,7 +189,8 @@ public class MarriedKeyChain extends DeterministicKeyChain {
for (DeterministicKey key : followingAccountKeys) {
checkArgument(key.getPath().size() == getAccountPath().size(), "Following keys have to be account keys");
DeterministicKeyChain chain = DeterministicKeyChain.builder().watchAndFollow(key).build();
DeterministicKeyChain chain = DeterministicKeyChain.builder().watchAndFollow(key)
.outputScriptType(getOutputScriptType()).build();
if (lookaheadSize >= 0)
chain.setLookaheadSize(lookaheadSize);
if (lookaheadThreshold >= 0)

View file

@ -2779,6 +2779,23 @@ public final class Protos {
* <code>repeated uint32 account_path = 10 [packed = true];</code>
*/
int getAccountPath(int index);
/**
* <pre>
* Type of addresses (aka output scripts) to generate for receiving.
* </pre>
*
* <code>optional .wallet.Key.OutputScriptType output_script_type = 11;</code>
*/
boolean hasOutputScriptType();
/**
* <pre>
* Type of addresses (aka output scripts) to generate for receiving.
* </pre>
*
* <code>optional .wallet.Key.OutputScriptType output_script_type = 11;</code>
*/
org.bitcoinj.wallet.Protos.Key.OutputScriptType getOutputScriptType();
}
/**
* <pre>
@ -2808,6 +2825,7 @@ public final class Protos {
creationTimestamp_ = 0L;
deterministicSeed_ = com.google.protobuf.ByteString.EMPTY;
accountPath_ = java.util.Collections.emptyList();
outputScriptType_ = 1;
}
@java.lang.Override
@ -2935,6 +2953,17 @@ public final class Protos {
input.popLimit(limit);
break;
}
case 88: {
int rawValue = input.readEnum();
org.bitcoinj.wallet.Protos.Key.OutputScriptType value = org.bitcoinj.wallet.Protos.Key.OutputScriptType.valueOf(rawValue);
if (value == null) {
unknownFields.mergeVarintField(11, rawValue);
} else {
bitField0_ |= 0x00000200;
outputScriptType_ = rawValue;
}
break;
}
}
}
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
@ -3116,6 +3145,96 @@ public final class Protos {
// @@protoc_insertion_point(enum_scope:wallet.Key.Type)
}
/**
* Protobuf enum {@code wallet.Key.OutputScriptType}
*/
public enum OutputScriptType
implements com.google.protobuf.ProtocolMessageEnum {
/**
* <code>P2PKH = 1;</code>
*/
P2PKH(1),
/**
* <code>P2WPKH = 2;</code>
*/
P2WPKH(2),
;
/**
* <code>P2PKH = 1;</code>
*/
public static final int P2PKH_VALUE = 1;
/**
* <code>P2WPKH = 2;</code>
*/
public static final int P2WPKH_VALUE = 2;
public final int getNumber() {
return value;
}
/**
* @deprecated Use {@link #forNumber(int)} instead.
*/
@java.lang.Deprecated
public static OutputScriptType valueOf(int value) {
return forNumber(value);
}
public static OutputScriptType forNumber(int value) {
switch (value) {
case 1: return P2PKH;
case 2: return P2WPKH;
default: return null;
}
}
public static com.google.protobuf.Internal.EnumLiteMap<OutputScriptType>
internalGetValueMap() {
return internalValueMap;
}
private static final com.google.protobuf.Internal.EnumLiteMap<
OutputScriptType> internalValueMap =
new com.google.protobuf.Internal.EnumLiteMap<OutputScriptType>() {
public OutputScriptType findValueByNumber(int number) {
return OutputScriptType.forNumber(number);
}
};
public final com.google.protobuf.Descriptors.EnumValueDescriptor
getValueDescriptor() {
return getDescriptor().getValues().get(ordinal());
}
public final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptorForType() {
return getDescriptor();
}
public static final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptor() {
return org.bitcoinj.wallet.Protos.Key.getDescriptor().getEnumTypes().get(1);
}
private static final OutputScriptType[] VALUES = values();
public static OutputScriptType valueOf(
com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
if (desc.getType() != getDescriptor()) {
throw new java.lang.IllegalArgumentException(
"EnumValueDescriptor is not for this type.");
}
return VALUES[desc.getIndex()];
}
private final int value;
private OutputScriptType(int value) {
this.value = value;
}
// @@protoc_insertion_point(enum_scope:wallet.Key.OutputScriptType)
}
private int bitField0_;
public static final int TYPE_FIELD_NUMBER = 1;
private int type_;
@ -3409,6 +3528,30 @@ public final class Protos {
}
private int accountPathMemoizedSerializedSize = -1;
public static final int OUTPUT_SCRIPT_TYPE_FIELD_NUMBER = 11;
private int outputScriptType_;
/**
* <pre>
* Type of addresses (aka output scripts) to generate for receiving.
* </pre>
*
* <code>optional .wallet.Key.OutputScriptType output_script_type = 11;</code>
*/
public boolean hasOutputScriptType() {
return ((bitField0_ & 0x00000200) == 0x00000200);
}
/**
* <pre>
* Type of addresses (aka output scripts) to generate for receiving.
* </pre>
*
* <code>optional .wallet.Key.OutputScriptType output_script_type = 11;</code>
*/
public org.bitcoinj.wallet.Protos.Key.OutputScriptType getOutputScriptType() {
org.bitcoinj.wallet.Protos.Key.OutputScriptType result = org.bitcoinj.wallet.Protos.Key.OutputScriptType.valueOf(outputScriptType_);
return result == null ? org.bitcoinj.wallet.Protos.Key.OutputScriptType.P2PKH : result;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
byte isInitialized = memoizedIsInitialized;
@ -3478,6 +3621,9 @@ public final class Protos {
for (int i = 0; i < accountPath_.size(); i++) {
output.writeUInt32NoTag(accountPath_.get(i));
}
if (((bitField0_ & 0x00000200) == 0x00000200)) {
output.writeEnum(11, outputScriptType_);
}
unknownFields.writeTo(output);
}
@ -3535,6 +3681,10 @@ public final class Protos {
}
accountPathMemoizedSerializedSize = dataSize;
}
if (((bitField0_ & 0x00000200) == 0x00000200)) {
size += com.google.protobuf.CodedOutputStream
.computeEnumSize(11, outputScriptType_);
}
size += unknownFields.getSerializedSize();
memoizedSize = size;
return size;
@ -3598,6 +3748,10 @@ public final class Protos {
}
result = result && getAccountPathList()
.equals(other.getAccountPathList());
result = result && (hasOutputScriptType() == other.hasOutputScriptType());
if (hasOutputScriptType()) {
result = result && outputScriptType_ == other.outputScriptType_;
}
result = result && unknownFields.equals(other.unknownFields);
return result;
}
@ -3650,6 +3804,10 @@ public final class Protos {
hash = (37 * hash) + ACCOUNT_PATH_FIELD_NUMBER;
hash = (53 * hash) + getAccountPathList().hashCode();
}
if (hasOutputScriptType()) {
hash = (37 * hash) + OUTPUT_SCRIPT_TYPE_FIELD_NUMBER;
hash = (53 * hash) + outputScriptType_;
}
hash = (29 * hash) + unknownFields.hashCode();
memoizedHashCode = hash;
return hash;
@ -3812,6 +3970,8 @@ public final class Protos {
bitField0_ = (bitField0_ & ~0x00000100);
accountPath_ = java.util.Collections.emptyList();
bitField0_ = (bitField0_ & ~0x00000200);
outputScriptType_ = 1;
bitField0_ = (bitField0_ & ~0x00000400);
return this;
}
@ -3889,6 +4049,10 @@ public final class Protos {
bitField0_ = (bitField0_ & ~0x00000200);
}
result.accountPath_ = accountPath_;
if (((from_bitField0_ & 0x00000400) == 0x00000400)) {
to_bitField0_ |= 0x00000200;
}
result.outputScriptType_ = outputScriptType_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@ -3970,6 +4134,9 @@ public final class Protos {
}
onChanged();
}
if (other.hasOutputScriptType()) {
setOutputScriptType(other.getOutputScriptType());
}
this.mergeUnknownFields(other.unknownFields);
onChanged();
return this;
@ -4888,6 +5055,58 @@ public final class Protos {
onChanged();
return this;
}
private int outputScriptType_ = 1;
/**
* <pre>
* Type of addresses (aka output scripts) to generate for receiving.
* </pre>
*
* <code>optional .wallet.Key.OutputScriptType output_script_type = 11;</code>
*/
public boolean hasOutputScriptType() {
return ((bitField0_ & 0x00000400) == 0x00000400);
}
/**
* <pre>
* Type of addresses (aka output scripts) to generate for receiving.
* </pre>
*
* <code>optional .wallet.Key.OutputScriptType output_script_type = 11;</code>
*/
public org.bitcoinj.wallet.Protos.Key.OutputScriptType getOutputScriptType() {
org.bitcoinj.wallet.Protos.Key.OutputScriptType result = org.bitcoinj.wallet.Protos.Key.OutputScriptType.valueOf(outputScriptType_);
return result == null ? org.bitcoinj.wallet.Protos.Key.OutputScriptType.P2PKH : result;
}
/**
* <pre>
* Type of addresses (aka output scripts) to generate for receiving.
* </pre>
*
* <code>optional .wallet.Key.OutputScriptType output_script_type = 11;</code>
*/
public Builder setOutputScriptType(org.bitcoinj.wallet.Protos.Key.OutputScriptType value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000400;
outputScriptType_ = value.getNumber();
onChanged();
return this;
}
/**
* <pre>
* Type of addresses (aka output scripts) to generate for receiving.
* </pre>
*
* <code>optional .wallet.Key.OutputScriptType output_script_type = 11;</code>
*/
public Builder clearOutputScriptType() {
bitField0_ = (bitField0_ & ~0x00000400);
outputScriptType_ = 1;
onChanged();
return this;
}
public final Builder setUnknownFields(
final com.google.protobuf.UnknownFieldSet unknownFields) {
return super.setUnknownFields(unknownFields);
@ -20418,7 +20637,7 @@ public final class Protos {
"ode\030\001 \002(\014\022\014\n\004path\030\002 \003(\r\022\026\n\016issued_subkey" +
"s\030\003 \001(\r\022\026\n\016lookahead_size\030\004 \001(\r\022\023\n\013isFol" +
"lowing\030\005 \001(\010\022\036\n\023sigsRequiredToSpend\030\006 \001(" +
"\r:\0011\"\264\003\n\003Key\022\036\n\004type\030\001 \002(\0162\020.wallet.Key." +
"\r:\0011\"\231\004\n\003Key\022\036\n\004type\030\001 \002(\0162\020.wallet.Key." +
"Type\022\024\n\014secret_bytes\030\002 \001(\014\022-\n\016encrypted_",
"data\030\006 \001(\0132\025.wallet.EncryptedData\022\022\n\npub" +
"lic_key\030\003 \001(\014\022\r\n\005label\030\004 \001(\t\022\032\n\022creation" +
@ -20426,70 +20645,72 @@ public final class Protos {
"\001(\0132\030.wallet.DeterministicKey\022\032\n\022determi" +
"nistic_seed\030\010 \001(\014\022;\n\034encrypted_determini" +
"stic_seed\030\t \001(\0132\025.wallet.EncryptedData\022\030" +
"\n\014account_path\030\n \003(\rB\002\020\001\"a\n\004Type\022\014\n\010ORIG" +
"INAL\020\001\022\030\n\024ENCRYPTED_SCRYPT_AES\020\002\022\032\n\026DETE" +
"RMINISTIC_MNEMONIC\020\003\022\025\n\021DETERMINISTIC_KE" +
"Y\020\004\"5\n\006Script\022\017\n\007program\030\001 \002(\014\022\032\n\022creati",
"on_timestamp\030\002 \002(\003\"\035\n\rScriptWitness\022\014\n\004d" +
"ata\030\001 \003(\014\"\272\001\n\020TransactionInput\022\"\n\032transa" +
"ction_out_point_hash\030\001 \002(\014\022#\n\033transactio" +
"n_out_point_index\030\002 \002(\r\022\024\n\014script_bytes\030" +
"\003 \002(\014\022\020\n\010sequence\030\004 \001(\r\022\r\n\005value\030\005 \001(\003\022&" +
"\n\007witness\030\006 \001(\0132\025.wallet.ScriptWitness\"\177" +
"\n\021TransactionOutput\022\r\n\005value\030\001 \002(\003\022\024\n\014sc" +
"ript_bytes\030\002 \002(\014\022!\n\031spent_by_transaction" +
"_hash\030\003 \001(\014\022\"\n\032spent_by_transaction_inde" +
"x\030\004 \001(\005\"\267\003\n\025TransactionConfidence\0220\n\004typ",
"e\030\001 \001(\0162\".wallet.TransactionConfidence.T" +
"ype\022\032\n\022appeared_at_height\030\002 \001(\005\022\036\n\026overr" +
"iding_transaction\030\003 \001(\014\022\r\n\005depth\030\004 \001(\005\022)" +
"\n\014broadcast_by\030\006 \003(\0132\023.wallet.PeerAddres" +
"s\022\033\n\023last_broadcasted_at\030\010 \001(\003\0224\n\006source" +
"\030\007 \001(\0162$.wallet.TransactionConfidence.So" +
"urce\"`\n\004Type\022\013\n\007UNKNOWN\020\000\022\014\n\010BUILDING\020\001\022" +
"\013\n\007PENDING\020\002\022\025\n\021NOT_IN_BEST_CHAIN\020\003\022\010\n\004D" +
"EAD\020\004\022\017\n\013IN_CONFLICT\020\005\"A\n\006Source\022\022\n\016SOUR" +
"CE_UNKNOWN\020\000\022\022\n\016SOURCE_NETWORK\020\001\022\017\n\013SOUR",
"CE_SELF\020\002\"\303\005\n\013Transaction\022\017\n\007version\030\001 \002" +
"(\005\022\014\n\004hash\030\002 \002(\014\022&\n\004pool\030\003 \001(\0162\030.wallet." +
"Transaction.Pool\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nu" +
"pdated_at\030\005 \001(\003\0223\n\021transaction_input\030\006 \003" +
"(\0132\030.wallet.TransactionInput\0225\n\022transact" +
"ion_output\030\007 \003(\0132\031.wallet.TransactionOut" +
"put\022\022\n\nblock_hash\030\010 \003(\014\022 \n\030block_relativ" +
"ity_offsets\030\013 \003(\005\0221\n\nconfidence\030\t \001(\0132\035." +
"wallet.TransactionConfidence\0225\n\007purpose\030" +
"\n \001(\0162\033.wallet.Transaction.Purpose:\007UNKN",
"OWN\022+\n\rexchange_rate\030\014 \001(\0132\024.wallet.Exch" +
"angeRate\022\014\n\004memo\030\r \001(\t\"Y\n\004Pool\022\013\n\007UNSPEN" +
"T\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACTIVE\020\002\022\010\n\004DEAD\020\n\022\013" +
"\n\007PENDING\020\020\022\024\n\020PENDING_INACTIVE\020\022\"\243\001\n\007Pu" +
"rpose\022\013\n\007UNKNOWN\020\000\022\020\n\014USER_PAYMENT\020\001\022\020\n\014" +
"KEY_ROTATION\020\002\022\034\n\030ASSURANCE_CONTRACT_CLA" +
"IM\020\003\022\035\n\031ASSURANCE_CONTRACT_PLEDGE\020\004\022\033\n\027A" +
"SSURANCE_CONTRACT_STUB\020\005\022\r\n\tRAISE_FEE\020\006\"" +
"N\n\020ScryptParameters\022\014\n\004salt\030\001 \002(\014\022\020\n\001n\030\002" +
" \001(\003:\00516384\022\014\n\001r\030\003 \001(\005:\0018\022\014\n\001p\030\004 \001(\005:\0011\"",
"8\n\tExtension\022\n\n\002id\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\022\021" +
"\n\tmandatory\030\003 \002(\010\" \n\003Tag\022\013\n\003tag\030\001 \002(\t\022\014\n" +
"\004data\030\002 \002(\014\"\261\004\n\006Wallet\022\032\n\022network_identi" +
"fier\030\001 \002(\t\022\034\n\024last_seen_block_hash\030\002 \001(\014" +
"\022\036\n\026last_seen_block_height\030\014 \001(\r\022!\n\031last" +
"_seen_block_time_secs\030\016 \001(\003\022\030\n\003key\030\003 \003(\013" +
"2\013.wallet.Key\022(\n\013transaction\030\004 \003(\0132\023.wal" +
"let.Transaction\022&\n\016watched_script\030\017 \003(\0132" +
"\016.wallet.Script\022C\n\017encryption_type\030\005 \001(\016" +
"2\035.wallet.Wallet.EncryptionType:\013UNENCRY",
"PTED\0227\n\025encryption_parameters\030\006 \001(\0132\030.wa" +
"llet.ScryptParameters\022\022\n\007version\030\007 \001(\005:\001" +
"1\022$\n\textension\030\n \003(\0132\021.wallet.Extension\022" +
"\023\n\013description\030\013 \001(\t\022\031\n\021key_rotation_tim" +
"e\030\r \001(\004\022\031\n\004tags\030\020 \003(\0132\013.wallet.Tag\";\n\016En" +
"cryptionType\022\017\n\013UNENCRYPTED\020\001\022\030\n\024ENCRYPT" +
"ED_SCRYPT_AES\020\002\"R\n\014ExchangeRate\022\022\n\ncoin_" +
"value\030\001 \002(\003\022\022\n\nfiat_value\030\002 \002(\003\022\032\n\022fiat_" +
"currency_code\030\003 \002(\tB\035\n\023org.bitcoinj.wall" +
"etB\006Protos"
"\n\014account_path\030\n \003(\rB\002\020\001\0228\n\022output_scrip" +
"t_type\030\013 \001(\0162\034.wallet.Key.OutputScriptTy" +
"pe\"a\n\004Type\022\014\n\010ORIGINAL\020\001\022\030\n\024ENCRYPTED_SC" +
"RYPT_AES\020\002\022\032\n\026DETERMINISTIC_MNEMONIC\020\003\022\025",
"\n\021DETERMINISTIC_KEY\020\004\")\n\020OutputScriptTyp" +
"e\022\t\n\005P2PKH\020\001\022\n\n\006P2WPKH\020\002\"5\n\006Script\022\017\n\007pr" +
"ogram\030\001 \002(\014\022\032\n\022creation_timestamp\030\002 \002(\003\"" +
"\035\n\rScriptWitness\022\014\n\004data\030\001 \003(\014\"\272\001\n\020Trans" +
"actionInput\022\"\n\032transaction_out_point_has" +
"h\030\001 \002(\014\022#\n\033transaction_out_point_index\030\002" +
" \002(\r\022\024\n\014script_bytes\030\003 \002(\014\022\020\n\010sequence\030\004" +
" \001(\r\022\r\n\005value\030\005 \001(\003\022&\n\007witness\030\006 \001(\0132\025.w" +
"allet.ScriptWitness\"\177\n\021TransactionOutput" +
"\022\r\n\005value\030\001 \002(\003\022\024\n\014script_bytes\030\002 \002(\014\022!\n",
"\031spent_by_transaction_hash\030\003 \001(\014\022\"\n\032spen" +
"t_by_transaction_index\030\004 \001(\005\"\267\003\n\025Transac" +
"tionConfidence\0220\n\004type\030\001 \001(\0162\".wallet.Tr" +
"ansactionConfidence.Type\022\032\n\022appeared_at_" +
"height\030\002 \001(\005\022\036\n\026overriding_transaction\030\003" +
" \001(\014\022\r\n\005depth\030\004 \001(\005\022)\n\014broadcast_by\030\006 \003(" +
"\0132\023.wallet.PeerAddress\022\033\n\023last_broadcast" +
"ed_at\030\010 \001(\003\0224\n\006source\030\007 \001(\0162$.wallet.Tra" +
"nsactionConfidence.Source\"`\n\004Type\022\013\n\007UNK" +
"NOWN\020\000\022\014\n\010BUILDING\020\001\022\013\n\007PENDING\020\002\022\025\n\021NOT",
"_IN_BEST_CHAIN\020\003\022\010\n\004DEAD\020\004\022\017\n\013IN_CONFLIC" +
"T\020\005\"A\n\006Source\022\022\n\016SOURCE_UNKNOWN\020\000\022\022\n\016SOU" +
"RCE_NETWORK\020\001\022\017\n\013SOURCE_SELF\020\002\"\303\005\n\013Trans" +
"action\022\017\n\007version\030\001 \002(\005\022\014\n\004hash\030\002 \002(\014\022&\n" +
"\004pool\030\003 \001(\0162\030.wallet.Transaction.Pool\022\021\n" +
"\tlock_time\030\004 \001(\r\022\022\n\nupdated_at\030\005 \001(\003\0223\n\021" +
"transaction_input\030\006 \003(\0132\030.wallet.Transac" +
"tionInput\0225\n\022transaction_output\030\007 \003(\0132\031." +
"wallet.TransactionOutput\022\022\n\nblock_hash\030\010" +
" \003(\014\022 \n\030block_relativity_offsets\030\013 \003(\005\0221",
"\n\nconfidence\030\t \001(\0132\035.wallet.TransactionC" +
"onfidence\0225\n\007purpose\030\n \001(\0162\033.wallet.Tran" +
"saction.Purpose:\007UNKNOWN\022+\n\rexchange_rat" +
"e\030\014 \001(\0132\024.wallet.ExchangeRate\022\014\n\004memo\030\r " +
"\001(\t\"Y\n\004Pool\022\013\n\007UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010I" +
"NACTIVE\020\002\022\010\n\004DEAD\020\n\022\013\n\007PENDING\020\020\022\024\n\020PEND" +
"ING_INACTIVE\020\022\"\243\001\n\007Purpose\022\013\n\007UNKNOWN\020\000\022" +
"\020\n\014USER_PAYMENT\020\001\022\020\n\014KEY_ROTATION\020\002\022\034\n\030A" +
"SSURANCE_CONTRACT_CLAIM\020\003\022\035\n\031ASSURANCE_C" +
"ONTRACT_PLEDGE\020\004\022\033\n\027ASSURANCE_CONTRACT_S",
"TUB\020\005\022\r\n\tRAISE_FEE\020\006\"N\n\020ScryptParameters" +
"\022\014\n\004salt\030\001 \002(\014\022\020\n\001n\030\002 \001(\003:\00516384\022\014\n\001r\030\003 " +
"\001(\005:\0018\022\014\n\001p\030\004 \001(\005:\0011\"8\n\tExtension\022\n\n\002id\030" +
"\001 \002(\t\022\014\n\004data\030\002 \002(\014\022\021\n\tmandatory\030\003 \002(\010\" " +
"\n\003Tag\022\013\n\003tag\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\"\261\004\n\006Wal" +
"let\022\032\n\022network_identifier\030\001 \002(\t\022\034\n\024last_" +
"seen_block_hash\030\002 \001(\014\022\036\n\026last_seen_block" +
"_height\030\014 \001(\r\022!\n\031last_seen_block_time_se" +
"cs\030\016 \001(\003\022\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013tr" +
"ansaction\030\004 \003(\0132\023.wallet.Transaction\022&\n\016",
"watched_script\030\017 \003(\0132\016.wallet.Script\022C\n\017" +
"encryption_type\030\005 \001(\0162\035.wallet.Wallet.En" +
"cryptionType:\013UNENCRYPTED\0227\n\025encryption_" +
"parameters\030\006 \001(\0132\030.wallet.ScryptParamete" +
"rs\022\022\n\007version\030\007 \001(\005:\0011\022$\n\textension\030\n \003(" +
"\0132\021.wallet.Extension\022\023\n\013description\030\013 \001(" +
"\t\022\031\n\021key_rotation_time\030\r \001(\004\022\031\n\004tags\030\020 \003" +
"(\0132\013.wallet.Tag\";\n\016EncryptionType\022\017\n\013UNE" +
"NCRYPTED\020\001\022\030\n\024ENCRYPTED_SCRYPT_AES\020\002\"R\n\014" +
"ExchangeRate\022\022\n\ncoin_value\030\001 \002(\003\022\022\n\nfiat",
"_value\030\002 \002(\003\022\032\n\022fiat_currency_code\030\003 \002(\t" +
"B\035\n\023org.bitcoinj.walletB\006Protos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() {
@ -20526,7 +20747,7 @@ public final class Protos {
internal_static_wallet_Key_fieldAccessorTable = new
com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
internal_static_wallet_Key_descriptor,
new java.lang.String[] { "Type", "SecretBytes", "EncryptedData", "PublicKey", "Label", "CreationTimestamp", "DeterministicKey", "DeterministicSeed", "EncryptedDeterministicSeed", "AccountPath", });
new java.lang.String[] { "Type", "SecretBytes", "EncryptedData", "PublicKey", "Label", "CreationTimestamp", "DeterministicKey", "DeterministicSeed", "EncryptedDeterministicSeed", "AccountPath", "OutputScriptType", });
internal_static_wallet_Script_descriptor =
getDescriptor().getMessageTypes().get(4);
internal_static_wallet_Script_fieldAccessorTable = new

View file

@ -1,5 +1,6 @@
/*
* Copyright 2014 Kosta Korenkov
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -52,12 +53,13 @@ public class RedeemData {
}
/**
* Creates RedeemData for P2PKH or P2PK input. Provided key is a single private key needed
* to spend such inputs and provided program should be a proper CHECKSIG program.
* Creates RedeemData for P2PKH, P2WPKH or P2PK input. Provided key is a single private key needed
* to spend such inputs.
*/
public static RedeemData of(ECKey key, Script program) {
checkArgument(ScriptPattern.isPayToPubKeyHash(program) || ScriptPattern.isPayToPubKey(program));
return key != null ? new RedeemData(Collections.singletonList(key), program) : null;
public static RedeemData of(ECKey key, Script redeemScript) {
checkArgument(ScriptPattern.isPayToPubKeyHash(redeemScript)
|| ScriptPattern.isPayToWitnessPubKeyHash(redeemScript) || ScriptPattern.isPayToPubKey(redeemScript));
return key != null ? new RedeemData(Collections.singletonList(key), redeemScript) : null;
}
/**

View file

@ -26,6 +26,7 @@ import com.google.protobuf.*;
import net.jcip.annotations.*;
import org.bitcoinj.core.listeners.*;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Base58;
import org.bitcoinj.core.AbstractBlockChain;
import org.bitcoinj.core.BlockChain;
import org.bitcoinj.core.BloomFilter;
@ -75,6 +76,7 @@ import org.bouncycastle.crypto.params.*;
import javax.annotation.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
@ -251,18 +253,44 @@ public class Wallet extends BaseTaggableObject
* Creates a new, empty wallet with a randomly chosen seed and no transactions. Make sure to provide for sufficient
* backup! Any keys will be derived from the seed. If you want to restore a wallet from disk instead, see
* {@link #loadFromFile}.
* @param params network parameters
* @param outputScriptType type of addresses (aka output scripts) to generate for receiving
*/
public Wallet(NetworkParameters params) {
this(Context.getOrCreate(params));
public static Wallet createDeterministic(NetworkParameters params, Script.ScriptType outputScriptType) {
return createDeterministic(Context.getOrCreate(params), outputScriptType);
}
/**
* Creates a new, empty wallet with a randomly chosen seed and no transactions. Make sure to provide for sufficient
* backup! Any keys will be derived from the seed. If you want to restore a wallet from disk instead, see
* {@link #loadFromFile}.
* @param params network parameters
* @deprecated Use {@link #createDeterministic(NetworkParameters, ScriptType)}
*/
@Deprecated
public Wallet(NetworkParameters params) {
this(params, KeyChainGroup.builder(params).fromRandom(Script.ScriptType.P2PKH).build());
}
/**
* Creates a new, empty wallet with a randomly chosen seed and no transactions. Make sure to provide for sufficient
* backup! Any keys will be derived from the seed. If you want to restore a wallet from disk instead, see
* {@link #loadFromFile}.
* @param outputScriptType type of addresses (aka output scripts) to generate for receiving
*/
public static Wallet createDeterministic(Context context, Script.ScriptType outputScriptType) {
return new Wallet(context, KeyChainGroup.builder(context.getParams()).fromRandom(outputScriptType).build());
}
/**
* Creates a new, empty wallet with a randomly chosen seed and no transactions. Make sure to provide for sufficient
* backup! Any keys will be derived from the seed. If you want to restore a wallet from disk instead, see
* {@link #loadFromFile}.
* @deprecated Use {@link #createDeterministic(Context, ScriptType)}
*/
@Deprecated
public Wallet(Context context) {
this(context, KeyChainGroup.builder(context.getParams()).fromRandom().build());
this(context, KeyChainGroup.builder(context.getParams()).fromRandom(Script.ScriptType.P2PKH).build());
}
/**
@ -274,14 +302,52 @@ public class Wallet extends BaseTaggableObject
return new Wallet(params, KeyChainGroup.createBasic(params));
}
/**
* @param params network parameters
* @param seed deterministic seed
* @param outputScriptType type of addresses (aka output scripts) to generate for receiving
* @return a wallet from a deterministic seed with a default account path
*/
public static Wallet fromSeed(NetworkParameters params, DeterministicSeed seed,
Script.ScriptType outputScriptType) {
return fromSeed(params, seed, outputScriptType, KeyChainGroupStructure.DEFAULT);
}
/**
* @param params network parameters
* @param seed deterministic seed
* @param outputScriptType type of addresses (aka output scripts) to generate for receiving
* @param structure structure for your wallet
* @return a wallet from a deterministic seed with a default account path
*/
public static Wallet fromSeed(NetworkParameters params, DeterministicSeed seed, Script.ScriptType outputScriptType,
KeyChainGroupStructure structure) {
return new Wallet(params, KeyChainGroup.builder(params, structure).fromSeed(seed, outputScriptType).build());
}
/**
* @param params network parameters
* @param seed deterministic seed
* @return a wallet from a deterministic seed with a
* {@link DeterministicKeyChain#ACCOUNT_ZERO_PATH 0 hardened path}
* @deprecated Use {@link #fromSeed(NetworkParameters, DeterministicSeed, ScriptType, KeyChainGroupStructure)}
*/
@Deprecated
public static Wallet fromSeed(NetworkParameters params, DeterministicSeed seed) {
DeterministicKeyChain chain = DeterministicKeyChain.builder().seed(seed).build();
return fromSeed(params, seed, Script.ScriptType.P2PKH);
}
/**
* @param params network parameters
* @param seed deterministic seed
* @param outputScriptType type of addresses (aka output scripts) to generate for receiving
* @param accountPath account path to generate receiving addresses on
* @return an instance of a wallet from a deterministic seed.
*/
public static Wallet fromSeed(NetworkParameters params, DeterministicSeed seed, Script.ScriptType outputScriptType,
ImmutableList<ChildNumber> accountPath) {
DeterministicKeyChain chain = DeterministicKeyChain.builder().seed(seed).outputScriptType(outputScriptType)
.accountPath(accountPath).build();
return new Wallet(params, KeyChainGroup.builder(params).addChain(chain).build());
}
@ -290,21 +356,34 @@ public class Wallet extends BaseTaggableObject
* @param seed deterministic seed
* @param accountPath account path
* @return an instance of a wallet from a deterministic seed.
* @deprecated Use {@link #fromSeed(NetworkParameters, DeterministicSeed, ScriptType, ImmutableList)}
*/
@Deprecated
public static Wallet fromSeed(NetworkParameters params, DeterministicSeed seed, ImmutableList<ChildNumber> accountPath) {
DeterministicKeyChain chain = DeterministicKeyChain.builder().seed(seed).accountPath(accountPath).build();
return new Wallet(params, KeyChainGroup.builder(params).addChain(chain).build());
return fromSeed(params, seed, Script.ScriptType.P2PKH, accountPath);
}
/**
* Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given watching key. This HAS
* to be an account key as returned by {@link DeterministicKeyChain#getWatchingKey()}.
*/
public static Wallet fromWatchingKey(NetworkParameters params, DeterministicKey watchKey) {
DeterministicKeyChain chain = DeterministicKeyChain.builder().watch(watchKey).build();
public static Wallet fromWatchingKey(NetworkParameters params, DeterministicKey watchKey,
Script.ScriptType outputScriptType) {
DeterministicKeyChain chain = DeterministicKeyChain.builder().watch(watchKey).outputScriptType(outputScriptType)
.build();
return new Wallet(params, KeyChainGroup.builder(params).addChain(chain).build());
}
/**
* Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given watching key. This HAS
* to be an account key as returned by {@link DeterministicKeyChain#getWatchingKey()}.
* @deprecated Use {@link #fromWatchingKey(NetworkParameters, DeterministicKey, ScriptType)}
*/
@Deprecated
public static Wallet fromWatchingKey(NetworkParameters params, DeterministicKey watchKey) {
return fromWatchingKey(params, watchKey, Script.ScriptType.P2PKH);
}
/**
* Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given watching key. The
* account path is specified. The key is specified in base58 notation and the creation time of the key. If you don't
@ -313,18 +392,30 @@ public class Wallet extends BaseTaggableObject
public static Wallet fromWatchingKeyB58(NetworkParameters params, String watchKeyB58, long creationTimeSeconds) {
final DeterministicKey watchKey = DeterministicKey.deserializeB58(null, watchKeyB58, params);
watchKey.setCreationTimeSeconds(creationTimeSeconds);
return fromWatchingKey(params, watchKey);
return fromWatchingKey(params, watchKey, outputScriptTypeFromB58(params, watchKeyB58));
}
/**
* Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given spending key. This HAS
* to be an account key as returned by {@link DeterministicKeyChain#getWatchingKey()}. This wallet can also spend.
*/
public static Wallet fromSpendingKey(NetworkParameters params, DeterministicKey spendKey) {
DeterministicKeyChain chain = DeterministicKeyChain.builder().spend(spendKey).build();
public static Wallet fromSpendingKey(NetworkParameters params, DeterministicKey spendKey,
Script.ScriptType outputScriptType) {
DeterministicKeyChain chain = DeterministicKeyChain.builder().spend(spendKey).outputScriptType(outputScriptType)
.build();
return new Wallet(params, KeyChainGroup.builder(params).addChain(chain).build());
}
/**
* Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given spending key. This HAS
* to be an account key as returned by {@link DeterministicKeyChain#getWatchingKey()}. This wallet can also spend.
* @deprecated Use {@link #fromSpendingKey(NetworkParameters, DeterministicKey, ScriptType)}
*/
@Deprecated
public static Wallet fromSpendingKey(NetworkParameters params, DeterministicKey spendKey) {
return fromSpendingKey(params, spendKey, Script.ScriptType.P2PKH);
}
/**
* Creates a wallet that tracks payments to and from the HD key hierarchy rooted by the given spending key.
* The key is specified in base58 notation and the creation time of the key. If you don't know the creation time,
@ -333,7 +424,7 @@ public class Wallet extends BaseTaggableObject
public static Wallet fromSpendingKeyB58(NetworkParameters params, String spendingKeyB58, long creationTimeSeconds) {
final DeterministicKey spendKey = DeterministicKey.deserializeB58(null, spendingKeyB58, params);
spendKey.setCreationTimeSeconds(creationTimeSeconds);
return fromSpendingKey(params, spendKey);
return fromSpendingKey(params, spendKey, outputScriptTypeFromB58(params, spendingKeyB58));
}
/**
@ -341,11 +432,12 @@ public class Wallet extends BaseTaggableObject
* to be an account key as returned by {@link DeterministicKeyChain#getWatchingKey()}.
*/
public static Wallet fromMasterKey(NetworkParameters params, DeterministicKey masterKey,
ChildNumber accountNumber) {
Script.ScriptType outputScriptType, ChildNumber accountNumber) {
DeterministicKey accountKey = HDKeyDerivation.deriveChildKey(masterKey, accountNumber);
accountKey = accountKey.dropParent();
accountKey.setCreationTimeSeconds(masterKey.getCreationTimeSeconds());
DeterministicKeyChain chain = DeterministicKeyChain.builder().spend(accountKey).build();
DeterministicKeyChain chain = DeterministicKeyChain.builder().spend(accountKey)
.outputScriptType(outputScriptType).build();
return new Wallet(params, KeyChainGroup.builder(params).addChain(chain).build());
}
@ -361,6 +453,16 @@ public class Wallet extends BaseTaggableObject
return new Wallet(params, group);
}
private static Script.ScriptType outputScriptTypeFromB58(NetworkParameters params, String base58) {
int header = ByteBuffer.wrap(Base58.decodeChecked(base58)).getInt();
if (header == params.getBip32HeaderP2PKHpub() || header == params.getBip32HeaderP2PKHpriv())
return Script.ScriptType.P2PKH;
else if (header == params.getBip32HeaderP2WPKHpub() || header == params.getBip32HeaderP2WPKHpriv())
return Script.ScriptType.P2WPKH;
else
throw new IllegalArgumentException(base58.substring(0, 4));
}
public Wallet(NetworkParameters params, KeyChainGroup keyChainGroup) {
this(Context.getOrCreate(params), keyChainGroup);
}
@ -590,11 +692,18 @@ public class Wallet extends BaseTaggableObject
* {@link #currentReceiveKey()} or {@link #currentReceiveAddress()}.
*/
public List<Address> getIssuedReceiveAddresses() {
final List<ECKey> keys = getIssuedReceiveKeys();
List<Address> addresses = new ArrayList<>(keys.size());
for (ECKey key : keys)
addresses.add(LegacyAddress.fromKey(getParams(), key));
return addresses;
keyChainGroupLock.lock();
try {
final DeterministicKeyChain activeKeyChain = keyChainGroup.getActiveKeyChain();
final List<ECKey> keys = activeKeyChain.getIssuedReceiveKeys();
final Script.ScriptType outputScriptType = activeKeyChain.getOutputScriptType();
List<Address> addresses = new ArrayList<>(keys.size());
for (ECKey key : keys)
addresses.add(Address.fromKey(getParams(), key, outputScriptType));
return addresses;
} finally {
keyChainGroupLock.unlock();
}
}
/**
@ -1015,10 +1124,10 @@ public class Wallet extends BaseTaggableObject
*/
@Override
@Nullable
public ECKey findKeyFromPubKeyHash(byte[] pubKeyHash) {
public ECKey findKeyFromPubKeyHash(byte[] pubKeyHash, @Nullable Script.ScriptType scriptType) {
keyChainGroupLock.lock();
try {
return keyChainGroup.findKeyFromPubKeyHash(pubKeyHash);
return keyChainGroup.findKeyFromPubKeyHash(pubKeyHash, scriptType);
} finally {
keyChainGroupLock.unlock();
}
@ -1040,14 +1149,14 @@ public class Wallet extends BaseTaggableObject
public boolean isAddressMine(Address address) {
final ScriptType scriptType = address.getOutputScriptType();
if (scriptType == ScriptType.P2PKH || scriptType == ScriptType.P2WPKH)
return isPubKeyHashMine(address.getHash());
return isPubKeyHashMine(address.getHash(), scriptType);
else
throw new IllegalArgumentException(address.toString());
}
@Override
public boolean isPubKeyHashMine(byte[] pubKeyHash) {
return findKeyFromPubKeyHash(pubKeyHash) != null;
public boolean isPubKeyHashMine(byte[] pubKeyHash, @Nullable Script.ScriptType scriptType) {
return findKeyFromPubKeyHash(pubKeyHash, scriptType) != null;
}
@Override
@ -1067,7 +1176,7 @@ public class Wallet extends BaseTaggableObject
public ECKey findKeyFromAddress(Address address) {
final ScriptType scriptType = address.getOutputScriptType();
if (scriptType == ScriptType.P2PKH || scriptType == ScriptType.P2WPKH)
return findKeyFromPubKeyHash(address.getHash());
return findKeyFromPubKeyHash(address.getHash(), scriptType);
else
return null;
}
@ -1132,6 +1241,9 @@ public class Wallet extends BaseTaggableObject
LegacyAddress a = LegacyAddress.fromScriptHash(tx.getParams(),
ScriptPattern.extractHashFromPayToScriptHash(script));
keyChainGroup.markP2SHAddressAsUsed(a);
} else if (ScriptPattern.isPayToWitnessHash(script)) {
byte[] pubkeyHash = ScriptPattern.extractHashFromPayToWitnessHash(script);
keyChainGroup.markPubKeyHashAsUsed(pubkeyHash);
}
} catch (ScriptException e) {
// Just means we didn't understand the output of this transaction: ignore it.
@ -4082,16 +4194,19 @@ public class Wallet extends BaseTaggableObject
int numInputs = tx.getInputs().size();
for (int i = 0; i < numInputs; i++) {
TransactionInput txIn = tx.getInput(i);
if (txIn.getConnectedOutput() == null) {
TransactionOutput connectedOutput = txIn.getConnectedOutput();
if (connectedOutput == null) {
// Missing connected output, assuming already signed.
continue;
}
Script scriptPubKey = connectedOutput.getScriptPubKey();
try {
// We assume if its already signed, its hopefully got a SIGHASH type that will not invalidate when
// we sign missing pieces (to check this would require either assuming any signatures are signing
// standard output types or a way to get processed signatures out of script execution)
txIn.getScriptSig().correctlySpends(tx, i, txIn.getConnectedOutput().getScriptPubKey());
txIn.getScriptSig().correctlySpends(tx, i, txIn.getWitness(), connectedOutput.getValue(),
connectedOutput.getScriptPubKey(), Script.ALL_VERIFY_FLAGS);
log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", i);
continue;
} catch (ScriptException e) {
@ -4099,10 +4214,10 @@ public class Wallet extends BaseTaggableObject
// Expected.
}
Script scriptPubKey = txIn.getConnectedOutput().getScriptPubKey();
RedeemData redeemData = txIn.getConnectedRedeemData(maybeDecryptingKeyBag);
checkNotNull(redeemData, "Transaction exists in wallet that we cannot redeem: %s", txIn.getOutpoint().getHash());
txIn.setScriptSig(scriptPubKey.createEmptyInputScript(redeemData.keys.get(0), redeemData.redeemScript));
txIn.setWitness(scriptPubKey.createEmptyWitness(redeemData.keys.get(0)));
}
TransactionSigner.ProposedTransaction proposal = new TransactionSigner.ProposedTransaction(tx);
@ -4181,8 +4296,13 @@ public class Wallet extends BaseTaggableObject
RedeemData data = findRedeemDataFromScriptHash(ScriptPattern.extractHashFromPayToScriptHash(script));
return data != null && canSignFor(data.redeemScript);
} else if (ScriptPattern.isPayToPubKeyHash(script)) {
ECKey key = findKeyFromPubKeyHash(ScriptPattern.extractHashFromPayToPubKeyHash(script));
ECKey key = findKeyFromPubKeyHash(ScriptPattern.extractHashFromPayToPubKeyHash(script),
Script.ScriptType.P2PKH);
return key != null && (key.isEncrypted() || key.hasPrivKey());
} else if (ScriptPattern.isPayToWitnessPubKeyHash(script)) {
ECKey key = findKeyFromPubKeyHash(ScriptPattern.extractHashFromPayToWitnessHash(script),
Script.ScriptType.P2WPKH);
return key != null && (key.isEncrypted() || key.hasPrivKey()) && key.isCompressed();
} else if (ScriptPattern.isSentToMultisig(script)) {
for (ECKey pubkey : script.getPubKeys()) {
ECKey key = findKeyFromPubKey(pubkey.getPubKey());
@ -4645,7 +4765,12 @@ public class Wallet extends BaseTaggableObject
// before calling, but because this is public API we must still lock again regardless.
keyChainGroupLock.lock();
try {
return !watchedScripts.isEmpty();
if (!watchedScripts.isEmpty())
return true;
for (DeterministicKeyChain chain : keyChainGroup.chains)
if (chain.getOutputScriptType() == Script.ScriptType.P2WPKH)
return true;
return false;
} finally {
keyChainGroupLock.unlock();
}
@ -4701,7 +4826,8 @@ public class Wallet extends BaseTaggableObject
// Returns true if the output is one that won't be selected by a data element matching in the scriptSig.
private boolean isTxOutputBloomFilterable(TransactionOutput out) {
Script script = out.getScriptPubKey();
boolean isScriptTypeSupported = ScriptPattern.isPayToPubKey(script) || ScriptPattern.isPayToScriptHash(script);
boolean isScriptTypeSupported = ScriptPattern.isPayToPubKey(script) || ScriptPattern.isPayToScriptHash(script)
|| ScriptPattern.isPayToWitnessPubKeyHash(script) || ScriptPattern.isPayToWitnessScriptHash(script);
return (isScriptTypeSupported && myUnspents.contains(out)) || watchedScripts.contains(script);
}
@ -4959,7 +5085,12 @@ public class Wallet extends BaseTaggableObject
ECKey key = null;
Script redeemScript = null;
if (ScriptPattern.isPayToPubKeyHash(script)) {
key = findKeyFromPubKeyHash(ScriptPattern.extractHashFromPayToPubKeyHash(script));
key = findKeyFromPubKeyHash(ScriptPattern.extractHashFromPayToPubKeyHash(script),
Script.ScriptType.P2PKH);
checkNotNull(key, "Coin selection includes unspendable outputs");
} else if (ScriptPattern.isPayToWitnessPubKeyHash(script)) {
key = findKeyFromPubKeyHash(ScriptPattern.extractHashFromPayToWitnessHash(script),
Script.ScriptType.P2WPKH);
checkNotNull(key, "Coin selection includes unspendable outputs");
} else if (ScriptPattern.isPayToScriptHash(script)) {
redeemScript = findRedeemDataFromScriptHash(ScriptPattern.extractHashFromPayToScriptHash(script)).redeemScript;

View file

@ -137,6 +137,13 @@ message Key {
// The path to the root. Only applicable to a DETERMINISTIC_MNEMONIC key entry.
repeated uint32 account_path = 10 [packed = true];
enum OutputScriptType {
P2PKH = 1;
P2WPKH = 2;
}
// Type of addresses (aka output scripts) to generate for receiving.
optional OutputScriptType output_script_type = 11;
}
message Script {

View file

@ -22,8 +22,10 @@ import com.google.common.collect.*;
import com.google.common.util.concurrent.*;
import org.bitcoinj.core.listeners.*;
import org.bitcoinj.net.discovery.*;
import org.bitcoinj.script.Script;
import org.bitcoinj.testing.*;
import org.bitcoinj.utils.*;
import org.bitcoinj.wallet.KeyChainGroupStructure;
import org.bitcoinj.wallet.Wallet;
import org.junit.*;
import org.junit.runner.*;
@ -781,7 +783,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
final int NUM_KEYS = 9;
// First, grab a load of keys from the wallet, and then recreate it so it forgets that those keys were issued.
Wallet shadow = Wallet.fromSeed(wallet.getParams(), wallet.getKeyChainSeed());
Wallet shadow = Wallet.fromSeed(wallet.getParams(), wallet.getKeyChainSeed(), Script.ScriptType.P2PKH);
List<ECKey> keys = new ArrayList<>(NUM_KEYS);
for (int i = 0; i < NUM_KEYS; i++) {
keys.add(shadow.freshReceiveKey());

View file

@ -324,18 +324,21 @@ public class TransactionTest {
assertEquals(txHex, HEX.encode(tx.bitcoinSerialize()));
assertEquals(2, tx.getInputs().size());
assertEquals(2, tx.getOutputs().size());
TransactionInput txIn0 = tx.getInput(0);
TransactionInput txIn1 = tx.getInput(1);
ECKey key0 = ECKey.fromPrivate(
HEX.decode("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"));
ECKey key0 = ECKey.fromPrivate(HEX.decode("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"));
Script scriptPubKey0 = ScriptBuilder.createP2PKOutputScript(key0);
assertEquals("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac",
HEX.encode(ScriptBuilder.createP2PKOutputScript(key0).getProgram()));
ECKey key1 = ECKey.fromPrivate(
HEX.decode("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"));
assertEquals("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357",
key1.getPublicKeyAsHex());
HEX.encode(scriptPubKey0.getProgram()));
ECKey key1 = ECKey.fromPrivate(HEX.decode("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"));
assertEquals("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357", key1.getPublicKeyAsHex());
Script scriptPubKey1 = ScriptBuilder.createP2WPKHOutputScript(key1);
assertEquals("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1", HEX.encode(scriptPubKey1.getProgram()));
txIn1.connect(new TransactionOutput(TESTNET, null, Coin.COIN.multiply(6), scriptPubKey1.getProgram()));
TransactionSignature txSig0 = tx.calculateSignature(0, key0,
ScriptBuilder.createP2PKOutputScript(key0).getProgram(),
scriptPubKey0,
Transaction.SigHash.ALL, false);
assertEquals("30450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01",
HEX.encode(txSig0.encodeToBitcoin()));
@ -345,24 +348,20 @@ public class TransactionTest {
HEX.encode(scriptCode.getProgram()));
TransactionSignature txSig1 = tx.calculateWitnessSignature(1, key1,
scriptCode, Coin.COIN.multiply(6),
scriptCode, txIn1.getValue(),
Transaction.SigHash.ALL, false);
assertEquals("304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee"
+ "01",
HEX.encode(txSig1.encodeToBitcoin()));
TransactionInput txIn0 = tx.getInput(0);
txIn0.setScriptSig(new ScriptBuilder()
.data(txSig0.encodeToBitcoin())
.build());
assertFalse(correctlySpends(txIn0, scriptPubKey0, 0));
txIn0.setScriptSig(new ScriptBuilder().data(txSig0.encodeToBitcoin()).build());
assertTrue(correctlySpends(txIn0, scriptPubKey0, 0));
TransactionWitness witness = new TransactionWitness(2);
witness.setPush(0, txSig1.encodeToBitcoin());
witness.setPush(1, key1.getPubKey());
TransactionInput txIn1 = tx.getInput(1);
txIn1.setWitness(witness);
assertFalse(correctlySpends(txIn1, scriptPubKey1, 1));
txIn1.setWitness(TransactionWitness.redeemP2WPKH(txSig1, key1));
// no redeem script for p2wpkh
assertTrue(correctlySpends(txIn1, scriptPubKey1, 1));
String signedTxHex = "01000000" // version
+ "00" // marker
@ -400,6 +399,7 @@ public class TransactionTest {
assertEquals(txHex, HEX.encode(tx.bitcoinSerialize()));
assertEquals(1, tx.getInputs().size());
assertEquals(2, tx.getOutputs().size());
TransactionInput txIn = tx.getInput(0);
ECKey key = ECKey.fromPrivate(
HEX.decode("eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"));
@ -415,10 +415,7 @@ public class TransactionTest {
assertEquals("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387",
HEX.encode(scriptPubKey.getProgram()));
Script scriptCode = new ScriptBuilder()
.data(ScriptBuilder.createOutputScript(LegacyAddress.fromKey(TESTNET, key))
.getProgram())
.build();
Script scriptCode = new ScriptBuilder().data(ScriptBuilder.createP2PKHOutputScript(key).getProgram()).build();
assertEquals("1976a91479091972186c449eb1ded22b78e40d009bdf008988ac",
HEX.encode(scriptCode.getProgram()));
@ -429,13 +426,10 @@ public class TransactionTest {
+ "01",
HEX.encode(txSig.encodeToBitcoin()));
TransactionWitness witness = new TransactionWitness(2);
witness.setPush(0, txSig.encodeToBitcoin());
witness.setPush(1, key.getPubKey());
TransactionInput txIn = tx.getInput(0);
txIn.setWitness(witness);
assertFalse(correctlySpends(txIn, scriptPubKey, 0));
txIn.setWitness(TransactionWitness.redeemP2WPKH(txSig, key));
txIn.setScriptSig(new ScriptBuilder().data(redeemScript.getProgram()).build());
assertTrue(correctlySpends(txIn, scriptPubKey, 0));
String signedTxHex = "01000000" // version
+ "00" // marker
@ -455,6 +449,16 @@ public class TransactionTest {
assertEquals(signedTxHex, HEX.encode(tx.bitcoinSerialize()));
}
private boolean correctlySpends(TransactionInput txIn, Script scriptPubKey, int inputIndex) {
try {
txIn.getScriptSig().correctlySpends(txIn.getParentTransaction(), inputIndex, txIn.getWitness(),
txIn.getValue(), scriptPubKey, Script.ALL_VERIFY_FLAGS);
return true;
} catch (ScriptException x) {
return false;
}
}
@Test
public void testToStringWhenLockTimeIsSpecifiedInBlockHeight() {
Transaction tx = FakeTxBuilder.createFakeTx(UNITTEST);

View file

@ -109,12 +109,10 @@ public class WalletProtobufSerializerTest {
Wallet wallet1 = roundTrip(myWallet);
assertEquals(0, wallet1.getTransactions(true).size());
assertEquals(Coin.ZERO, wallet1.getBalance());
assertArrayEquals(myKey.getPubKey(),
wallet1.findKeyFromPubKeyHash(myKey.getPubKeyHash()).getPubKey());
assertArrayEquals(myKey.getPrivKeyBytes(),
wallet1.findKeyFromPubKeyHash(myKey.getPubKeyHash()).getPrivKeyBytes());
assertEquals(myKey.getCreationTimeSeconds(),
wallet1.findKeyFromPubKeyHash(myKey.getPubKeyHash()).getCreationTimeSeconds());
ECKey foundKey = wallet1.findKeyFromPubKeyHash(myKey.getPubKeyHash(), null);
assertArrayEquals(myKey.getPubKey(), foundKey.getPubKey());
assertArrayEquals(myKey.getPrivKeyBytes(), foundKey.getPrivKeyBytes());
assertEquals(myKey.getCreationTimeSeconds(), foundKey.getCreationTimeSeconds());
assertEquals(mScriptCreationTime,
wallet1.getWatchedScripts().get(0).getCreationTimeSeconds());
assertEquals(1, wallet1.getWatchedScripts().size());
@ -197,8 +195,9 @@ public class WalletProtobufSerializerTest {
myWallet = new Wallet(UNITTEST);
myWallet.importKey(myKey);
Wallet wallet1 = roundTrip(myWallet);
assertArrayEquals(myKey.getPubKey(), wallet1.findKeyFromPubKeyHash(myKey.getPubKeyHash()).getPubKey());
assertArrayEquals(myKey.getPrivKeyBytes(), wallet1.findKeyFromPubKeyHash(myKey.getPubKeyHash()).getPrivKeyBytes());
ECKey foundKey = wallet1.findKeyFromPubKeyHash(myKey.getPubKeyHash(), null);
assertArrayEquals(myKey.getPubKey(), foundKey.getPubKey());
assertArrayEquals(myKey.getPrivKeyBytes(), foundKey.getPrivKeyBytes());
}
}
@ -333,12 +332,10 @@ public class WalletProtobufSerializerTest {
Wallet wallet1 = roundTrip(myWallet);
assertEquals(0, wallet1.getTransactions(true).size());
assertEquals(Coin.ZERO, wallet1.getBalance());
assertArrayEquals(myKey.getPubKey(),
wallet1.findKeyFromPubKeyHash(myKey.getPubKeyHash()).getPubKey());
assertArrayEquals(myKey.getPrivKeyBytes(),
wallet1.findKeyFromPubKeyHash(myKey.getPubKeyHash()).getPrivKeyBytes());
assertEquals(myKey.getCreationTimeSeconds(),
wallet1.findKeyFromPubKeyHash(myKey.getPubKeyHash()).getCreationTimeSeconds());
ECKey foundKey = wallet1.findKeyFromPubKeyHash(myKey.getPubKeyHash(), null);
assertArrayEquals(myKey.getPubKey(), foundKey.getPubKey());
assertArrayEquals(myKey.getPrivKeyBytes(), foundKey.getPrivKeyBytes());
assertEquals(myKey.getCreationTimeSeconds(), foundKey.getCreationTimeSeconds());
}
@Test

View file

@ -27,6 +27,7 @@ import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.*;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.utils.BriefLogFormatter;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.listeners.AbstractKeyChainEventListener;
@ -50,6 +51,7 @@ import static org.junit.Assert.*;
public class DeterministicKeyChainTest {
private DeterministicKeyChain chain;
private DeterministicKeyChain segwitChain;
private DeterministicKeyChain bip44chain;
private final byte[] ENTROPY = Sha256Hash.hash("don't use a string seed like this in real life".getBytes());
private static final NetworkParameters UNITTEST = UnitTestParams.get();
@ -63,13 +65,17 @@ public class DeterministicKeyChainTest {
// You should use a random seed instead. The secs constant comes from the unit test file, so we can compare
// serialized data properly.
long secs = 1389353062L;
chain = DeterministicKeyChain.builder().entropy(ENTROPY, secs).build();
chain = DeterministicKeyChain.builder().entropy(ENTROPY, secs)
.accountPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH).outputScriptType(Script.ScriptType.P2PKH).build();
chain.setLookaheadSize(10);
assertEquals(secs, checkNotNull(chain.getSeed()).getCreationTimeSeconds());
bip44chain = DeterministicKeyChain.builder().entropy(ENTROPY, secs).accountPath(BIP44_ACCOUNT_ONE_PATH).build();
segwitChain = DeterministicKeyChain.builder().entropy(ENTROPY, secs)
.accountPath(DeterministicKeyChain.ACCOUNT_ONE_PATH).outputScriptType(Script.ScriptType.P2WPKH).build();
segwitChain.setLookaheadSize(10);
bip44chain = DeterministicKeyChain.builder().entropy(ENTROPY, secs).accountPath(BIP44_ACCOUNT_ONE_PATH)
.outputScriptType(Script.ScriptType.P2PKH).build();
bip44chain.setLookaheadSize(10);
assertEquals(secs, checkNotNull(bip44chain.getSeed()).getCreationTimeSeconds());
}
@Test
@ -167,7 +173,8 @@ public class DeterministicKeyChainTest {
// Check that we get the right events at the right time.
final List<List<ECKey>> listenerKeys = Lists.newArrayList();
long secs = 1389353062L;
chain = DeterministicKeyChain.builder().entropy(ENTROPY, secs).build();
chain = DeterministicKeyChain.builder().entropy(ENTROPY, secs).outputScriptType(Script.ScriptType.P2PKH)
.build();
chain.addEventListener(new AbstractKeyChainEventListener() {
@Override
public void onKeysAdded(List<ECKey> keys) {
@ -247,6 +254,43 @@ public class DeterministicKeyChainTest {
assertEquals(oldLookaheadSize, chain.getLookaheadSize());
}
@Test
public void serializeSegwitUnencrypted() throws UnreadableWalletException {
segwitChain.maybeLookAhead();
DeterministicKey key1 = segwitChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key2 = segwitChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key3 = segwitChain.getKey(KeyChain.KeyPurpose.CHANGE);
List<Protos.Key> keys = segwitChain.serializeToProtobuf();
// 1 mnemonic/seed, 1 master key, 1 account key, 2 internal keys, 3 derived, 20 lookahead and 5 lookahead threshold.
int numItems =
1 // mnemonic/seed
+ 1 // master key
+ 1 // account key
+ 2 // ext/int parent keys
+ (segwitChain.getLookaheadSize() + segwitChain.getLookaheadThreshold()) * 2 // lookahead zone on each chain
;
assertEquals(numItems, keys.size());
// Get another key that will be lost during round-tripping, to ensure we can derive it again.
DeterministicKey key4 = segwitChain.getKey(KeyChain.KeyPurpose.CHANGE);
final String EXPECTED_SERIALIZATION = checkSerialization(keys, "deterministic-wallet-segwit-serialization.txt");
// Round trip the data back and forth to check it is preserved.
int oldLookaheadSize = segwitChain.getLookaheadSize();
segwitChain = DeterministicKeyChain.fromProtobuf(keys, null).get(0);
assertEquals(EXPECTED_SERIALIZATION, protoToString(segwitChain.serializeToProtobuf()));
assertEquals(key1, segwitChain.findKeyFromPubHash(key1.getPubKeyHash()));
assertEquals(key2, segwitChain.findKeyFromPubHash(key2.getPubKeyHash()));
assertEquals(key3, segwitChain.findKeyFromPubHash(key3.getPubKeyHash()));
assertEquals(key4, segwitChain.getKey(KeyChain.KeyPurpose.CHANGE));
key1.sign(Sha256Hash.ZERO_HASH);
key2.sign(Sha256Hash.ZERO_HASH);
key3.sign(Sha256Hash.ZERO_HASH);
key4.sign(Sha256Hash.ZERO_HASH);
assertEquals(oldLookaheadSize, segwitChain.getLookaheadSize());
}
@Test
public void serializeUnencryptedBIP44() throws UnreadableWalletException {
bip44chain.maybeLookAhead();
@ -358,7 +402,8 @@ public class DeterministicKeyChainTest {
assertEquals("xpub69KR9epSNBM59KLuasxMU5CyKytMJjBP5HEZ5p8YoGUCpM6cM9hqxB9DDPCpUUtqmw5duTckvPfwpoWGQUFPmRLpxs5jYiTf2u6xRMcdhDf", pub58);
watchingKey = DeterministicKey.deserializeB58(null, pub58, MAINNET);
watchingKey.setCreationTimeSeconds(100000);
chain = DeterministicKeyChain.builder().watch(watchingKey).build();
chain = DeterministicKeyChain.builder().watch(watchingKey).outputScriptType(chain.getOutputScriptType())
.build();
assertEquals(100000, chain.getEarliestKeyCreationTime());
chain.setLookaheadSize(10);
chain.maybeLookAhead();
@ -394,7 +439,8 @@ public class DeterministicKeyChainTest {
DeterministicKey watchingKey = bip44chain.getWatchingKey();
watchingKey = watchingKey.dropPrivateBytes().dropParent();
watchingKey.setCreationTimeSeconds(100000);
chain = DeterministicKeyChain.builder().watch(watchingKey).build();
chain = DeterministicKeyChain.builder().watch(watchingKey).outputScriptType(bip44chain.getOutputScriptType())
.build();
assertEquals(100000, chain.getEarliestKeyCreationTime());
chain.setLookaheadSize(10);
chain.maybeLookAhead();
@ -435,7 +481,8 @@ public class DeterministicKeyChainTest {
assertEquals("xpub69KR9epJ2Wp6ywiv4Xu5WfBUpX4GLu6D5NUMd4oUkCFoZoRNyk3ZCxfKPDkkGvCPa16dPgEdY63qoyLqEa5TQQy1nmfSmgWcagRzimyV7uA", pub58);
watchingKey = DeterministicKey.deserializeB58(null, pub58, MAINNET);
watchingKey.setCreationTimeSeconds(100000);
chain = DeterministicKeyChain.builder().watch(watchingKey).build();
chain = DeterministicKeyChain.builder().watch(watchingKey).outputScriptType(chain1.getOutputScriptType())
.build();
assertEquals(accountOne, chain.getAccountPath());
assertEquals(100000, chain.getEarliestKeyCreationTime());
chain.setLookaheadSize(10);
@ -461,6 +508,46 @@ public class DeterministicKeyChainTest {
assertEquals(key4.getPubKeyPoint(), rekey4.getPubKeyPoint());
}
@Test
public void watchingSegwitChain() throws UnreadableWalletException {
Utils.setMockClock();
DeterministicKey key1 = segwitChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key2 = segwitChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key3 = segwitChain.getKey(KeyChain.KeyPurpose.CHANGE);
DeterministicKey key4 = segwitChain.getKey(KeyChain.KeyPurpose.CHANGE);
DeterministicKey watchingKey = segwitChain.getWatchingKey();
final String pub58 = watchingKey.serializePubB58(MAINNET, segwitChain.getOutputScriptType());
assertEquals("zpub6nywkzAGfYS2siEfJtm9mo3hwDk8eUtL8EJ31XeWSd7C7x7esnfMMWmWiSs8od5jRt11arTjKLLbxCXuWNSXcxpi9PMSAphMt2ZE2gLnXGE", pub58);
watchingKey = DeterministicKey.deserializeB58(null, pub58, MAINNET);
watchingKey.setCreationTimeSeconds(100000);
segwitChain = DeterministicKeyChain.builder().watch(watchingKey)
.outputScriptType(segwitChain.getOutputScriptType()).build();
assertEquals(100000, segwitChain.getEarliestKeyCreationTime());
segwitChain.setLookaheadSize(10);
segwitChain.maybeLookAhead();
assertEquals(key1.getPubKeyPoint(), segwitChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKeyPoint());
assertEquals(key2.getPubKeyPoint(), segwitChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKeyPoint());
final DeterministicKey key = segwitChain.getKey(KeyChain.KeyPurpose.CHANGE);
assertEquals(key3.getPubKeyPoint(), key.getPubKeyPoint());
try {
// Can't sign with a key from a watching chain.
key.sign(Sha256Hash.ZERO_HASH);
fail();
} catch (ECKey.MissingPrivateKeyException e) {
// Ignored.
}
// Test we can serialize and deserialize a watching chain OK.
List<Protos.Key> serialization = segwitChain.serializeToProtobuf();
checkSerialization(serialization, "watching-wallet-p2wpkh-serialization.txt");
final DeterministicKeyChain chain = DeterministicKeyChain.fromProtobuf(serialization, null).get(0);
assertEquals(DeterministicKeyChain.ACCOUNT_ONE_PATH, chain.getAccountPath());
assertEquals(Script.ScriptType.P2WPKH, chain.getOutputScriptType());
final DeterministicKey rekey4 = segwitChain.getKey(KeyChain.KeyPurpose.CHANGE);
assertEquals(key4.getPubKeyPoint(), rekey4.getPubKeyPoint());
}
@Test
public void spendingChain() throws UnreadableWalletException {
Utils.setMockClock();
@ -475,7 +562,8 @@ public class DeterministicKeyChainTest {
assertEquals("xprv9vL4k9HYXonmvqGSUrRM6wGEmx3ruGTXi4JxHRiwEvwDwYmTocPbQNpjN89gpqPrFofmfvALwgnNFBCH2grse1YDf8ERAwgdvbjRtoMfsbV", prv58);
watchingKey = DeterministicKey.deserializeB58(null, prv58, params);
watchingKey.setCreationTimeSeconds(100000);
chain = DeterministicKeyChain.builder().spend(watchingKey).build();
chain = DeterministicKeyChain.builder().spend(watchingKey).outputScriptType(chain.getOutputScriptType())
.build();
assertEquals(100000, chain.getEarliestKeyCreationTime());
chain.setLookaheadSize(10);
chain.maybeLookAhead();
@ -517,7 +605,8 @@ public class DeterministicKeyChainTest {
assertEquals("xprv9vL4k9HYXonmzR7UC1ngJ3hTjxkmjLLUo3RexSfUGSWcACHzghWBLJAwW6xzs59XeFizQxFQWtscoTfrF9PSXrUgAtBgr13Nuojax8xTBRz", prv58);
watchingKey = DeterministicKey.deserializeB58(null, prv58, params);
watchingKey.setCreationTimeSeconds(secs);
chain = DeterministicKeyChain.builder().spend(watchingKey).build();
chain = DeterministicKeyChain.builder().spend(watchingKey).outputScriptType(chain.getOutputScriptType())
.build();
assertEquals(accountTwo, chain.getAccountPath());
assertEquals(secs, chain.getEarliestKeyCreationTime());
chain.setLookaheadSize(10);
@ -544,7 +633,8 @@ public class DeterministicKeyChainTest {
assertEquals("xprv9yYQhynAmWWuz62PScx5Q2frBET2F1raaXna5A2E9Lj8XWgmKBL7S98Yand8F736j9UCTNWQeiB4yL5pLZP7JDY2tY8eszGQkiKDwBkezeS", prv58);
watchingKey = DeterministicKey.deserializeB58(null, prv58, params);
watchingKey.setCreationTimeSeconds(secs);
DeterministicKeyChain fromPrivBase58Chain = DeterministicKeyChain.builder().spend(watchingKey).build();
DeterministicKeyChain fromPrivBase58Chain = DeterministicKeyChain.builder().spend(watchingKey)
.outputScriptType(bip44chain.getOutputScriptType()).build();
assertEquals(secs, fromPrivBase58Chain.getEarliestKeyCreationTime());
fromPrivBase58Chain.setLookaheadSize(10);
fromPrivBase58Chain.maybeLookAhead();
@ -555,8 +645,8 @@ public class DeterministicKeyChainTest {
DeterministicKey accountKey = HDKeyDerivation.deriveChildKey(coinLevelKey, new ChildNumber(0, true));
accountKey = accountKey.dropParent();
accountKey.setCreationTimeSeconds(watchingKey.getCreationTimeSeconds());
KeyChainGroup group = KeyChainGroup.builder(params)
.addChain(DeterministicKeyChain.builder().spend(accountKey).build()).build();
KeyChainGroup group = KeyChainGroup.builder(params).addChain(DeterministicKeyChain.builder().spend(accountKey)
.outputScriptType(bip44chain.getOutputScriptType()).build()).build();
DeterministicKeyChain fromMasterKeyChain = group.getActiveKeyChain();
assertEquals(BIP44_ACCOUNT_ONE_PATH, fromMasterKeyChain.getAccountPath());
assertEquals(secs, fromMasterKeyChain.getEarliestKeyCreationTime());
@ -611,7 +701,8 @@ public class DeterministicKeyChainTest {
@Test(expected = IllegalStateException.class)
public void watchingCannotEncrypt() throws Exception {
final DeterministicKey accountKey = chain.getKeyByPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH);
chain = DeterministicKeyChain.builder().watch(accountKey.dropPrivateBytes().dropParent()).build();
chain = DeterministicKeyChain.builder().watch(accountKey.dropPrivateBytes().dropParent())
.outputScriptType(chain.getOutputScriptType()).build();
assertEquals(DeterministicKeyChain.ACCOUNT_ZERO_PATH, chain.getAccountPath());
chain = chain.toEncrypted("this doesn't make any sense");
}
@ -642,7 +733,8 @@ public class DeterministicKeyChainTest {
DeterministicKey[] keys = new DeterministicKey[100];
for (int i = 0; i < keys.length; i++)
keys[i] = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
chain = DeterministicKeyChain.builder().watch(chain.getWatchingKey().dropPrivateBytes().dropParent()).build();
chain = DeterministicKeyChain.builder().watch(chain.getWatchingKey().dropPrivateBytes().dropParent())
.outputScriptType(chain.getOutputScriptType()).build();
int e = chain.numBloomFilterEntries();
BloomFilter filter = chain.getFilter(e, 0.001, 1);
for (DeterministicKey key : keys)

View file

@ -25,9 +25,11 @@ import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.*;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.Script.ScriptType;
import org.bitcoinj.utils.BriefLogFormatter;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.KeyChain.KeyPurpose;
import org.bitcoinj.wallet.listeners.KeyChainEventListener;
import com.google.common.collect.ImmutableList;
@ -49,6 +51,7 @@ public class KeyChainGroupTest {
private static final int LOOKAHEAD_SIZE = 5;
private static final NetworkParameters MAINNET = MainNetParams.get();
private static final String XPUB = "xpub68KFnj3bqUx1s7mHejLDBPywCAKdJEu1b49uniEEn2WSbHmZ7xbLqFTjJbtx1LUcAt1DwhoqWHmo2s5WMJp6wi38CiF2hYD49qVViKVvAoi";
private static final byte[] ENTROPY = Sha256Hash.hash("don't use a string seed like this in real life".getBytes());
private KeyChainGroup group;
private DeterministicKey watchingAccountKey;
@ -56,7 +59,7 @@ public class KeyChainGroupTest {
public void setup() {
BriefLogFormatter.init();
Utils.setMockClock();
group = KeyChainGroup.builder(MAINNET).fromRandom().build();
group = KeyChainGroup.builder(MAINNET).fromRandom(Script.ScriptType.P2PKH).build();
group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests.
group.getActiveKeyChain(); // Force create a chain.
@ -161,16 +164,16 @@ public class KeyChainGroupTest {
assertEquals(a, result);
result = group.findKeyFromPubKey(b.getPubKey());
assertEquals(b, result);
result = group.findKeyFromPubKeyHash(a.getPubKeyHash());
result = group.findKeyFromPubKeyHash(a.getPubKeyHash(), null);
assertEquals(a, result);
result = group.findKeyFromPubKeyHash(b.getPubKeyHash());
result = group.findKeyFromPubKeyHash(b.getPubKeyHash(), null);
assertEquals(b, result);
result = group.findKeyFromPubKey(c.getPubKey());
assertEquals(c, result);
result = group.findKeyFromPubKeyHash(c.getPubKeyHash());
result = group.findKeyFromPubKeyHash(c.getPubKeyHash(), null);
assertEquals(c, result);
assertNull(group.findKeyFromPubKey(d.getPubKey()));
assertNull(group.findKeyFromPubKeyHash(d.getPubKeyHash()));
assertNull(group.findKeyFromPubKeyHash(d.getPubKeyHash(), null));
}
@Test
@ -302,7 +305,7 @@ public class KeyChainGroupTest {
@Test
public void encryptionWhilstEmpty() throws Exception {
group = KeyChainGroup.builder(MAINNET).fromRandom().build();
group = KeyChainGroup.builder(MAINNET).fromRandom(Script.ScriptType.P2PKH).build();
group.setLookaheadSize(5);
KeyCrypterScrypt scrypt = new KeyCrypterScrypt(2);
final KeyParameter aesKey = scrypt.deriveKey("password");
@ -455,8 +458,8 @@ public class KeyChainGroupTest {
@Test
public void serializeWatching() throws Exception {
group = KeyChainGroup.builder(MAINNET)
.addChain(DeterministicKeyChain.builder().watch(watchingAccountKey).build()).build();
group = KeyChainGroup.builder(MAINNET).addChain(DeterministicKeyChain.builder().watch(watchingAccountKey)
.outputScriptType(Script.ScriptType.P2PKH).build()).build();
group.setLookaheadSize(LOOKAHEAD_SIZE);
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
group.freshKey(KeyChain.KeyPurpose.CHANGE);
@ -494,7 +497,8 @@ public class KeyChainGroupTest {
ECKey key1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
final DeterministicSeed seed = checkNotNull(group.getActiveKeyChain().getSeed());
KeyChainGroup group2 = KeyChainGroup.builder(MAINNET)
.addChain(DeterministicKeyChain.builder().seed(seed).build()).build();
.addChain(DeterministicKeyChain.builder().seed(seed).outputScriptType(Script.ScriptType.P2PKH).build())
.build();
group2.setLookaheadSize(5);
ECKey key2 = group2.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(key1, key2);
@ -597,7 +601,7 @@ public class KeyChainGroupTest {
@Test
public void isNotWatching() {
group = KeyChainGroup.builder(MAINNET).fromRandom().build();
group = KeyChainGroup.builder(MAINNET).fromRandom(Script.ScriptType.P2PKH).build();
final ECKey key = ECKey.fromPrivate(BigInteger.TEN);
group.importKeys(key);
assertFalse(group.isWatching());
@ -608,7 +612,7 @@ public class KeyChainGroupTest {
group = KeyChainGroup.builder(MAINNET)
.addChain(DeterministicKeyChain.builder().watch(DeterministicKey.deserializeB58(
"xpub69bjfJ91ikC5ghsqsVDHNq2dRGaV2HHVx7Y9LXi27LN9BWWAXPTQr4u8U3wAtap8bLdHdkqPpAcZmhMS5SnrMQC4ccaoBccFhh315P4UYzo",
MAINNET)).build())
MAINNET)).outputScriptType(Script.ScriptType.P2PKH).build())
.build();
final ECKey watchingKey = ECKey.fromPublicOnly(new ECKey().getPubKeyPoint());
group.importKeys(watchingKey);
@ -626,10 +630,46 @@ public class KeyChainGroupTest {
group = KeyChainGroup.builder(MAINNET)
.addChain(DeterministicKeyChain.builder().watch(DeterministicKey.deserializeB58(
"xpub69bjfJ91ikC5ghsqsVDHNq2dRGaV2HHVx7Y9LXi27LN9BWWAXPTQr4u8U3wAtap8bLdHdkqPpAcZmhMS5SnrMQC4ccaoBccFhh315P4UYzo",
MAINNET)).build())
MAINNET)).outputScriptType(Script.ScriptType.P2PKH).build())
.build();
final ECKey key = ECKey.fromPrivate(BigInteger.TEN);
group.importKeys(key);
group.isWatching();
}
@Test
public void segwitKeyChainGroup() throws Exception {
group = KeyChainGroup.builder(MAINNET).addChain(DeterministicKeyChain.builder().entropy(ENTROPY, 0)
.outputScriptType(Script.ScriptType.P2WPKH).accountPath(DeterministicKeyChain.ACCOUNT_ONE_PATH).build())
.build();
group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests.
assertEquals(Script.ScriptType.P2WPKH, group.getActiveKeyChain().getOutputScriptType());
assertEquals("bc1qhcurdec849thpjjp3e27atvya43gy2snrechd9",
group.currentAddress(KeyPurpose.RECEIVE_FUNDS).toString());
assertEquals("bc1qw8sf3mwuwn74qnhj83gjg0cwkk78fun2pxl9t2", group.currentAddress(KeyPurpose.CHANGE).toString());
// round-trip through protobuf
group = KeyChainGroup.fromProtobufUnencrypted(MAINNET, group.serializeToProtobuf());
assertEquals(Script.ScriptType.P2WPKH, group.getActiveKeyChain().getOutputScriptType());
assertEquals("bc1qhcurdec849thpjjp3e27atvya43gy2snrechd9",
group.currentAddress(KeyPurpose.RECEIVE_FUNDS).toString());
assertEquals("bc1qw8sf3mwuwn74qnhj83gjg0cwkk78fun2pxl9t2", group.currentAddress(KeyPurpose.CHANGE).toString());
// encryption
KeyCrypterScrypt scrypt = new KeyCrypterScrypt(2);
KeyParameter aesKey = scrypt.deriveKey("password");
group.encrypt(scrypt, aesKey);
assertEquals(Script.ScriptType.P2WPKH, group.getActiveKeyChain().getOutputScriptType());
assertEquals("bc1qhcurdec849thpjjp3e27atvya43gy2snrechd9",
group.currentAddress(KeyPurpose.RECEIVE_FUNDS).toString());
assertEquals("bc1qw8sf3mwuwn74qnhj83gjg0cwkk78fun2pxl9t2", group.currentAddress(KeyPurpose.CHANGE).toString());
// round-trip encrypted again, then dectypt
group = KeyChainGroup.fromProtobufEncrypted(MAINNET, group.serializeToProtobuf(), scrypt);
group.decrypt(aesKey);
assertEquals(Script.ScriptType.P2WPKH, group.getActiveKeyChain().getOutputScriptType());
assertEquals("bc1qhcurdec849thpjjp3e27atvya43gy2snrechd9",
group.currentAddress(KeyPurpose.RECEIVE_FUNDS).toString());
assertEquals("bc1qw8sf3mwuwn74qnhj83gjg0cwkk78fun2pxl9t2", group.currentAddress(KeyPurpose.CHANGE).toString());
}
}

View file

@ -27,6 +27,7 @@ import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.PeerAddress;
import org.bitcoinj.core.SegwitAddress;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
@ -62,6 +63,7 @@ import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.protobuf.ByteString;
import org.bitcoinj.wallet.KeyChain.KeyPurpose;
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
import org.junit.After;
import org.junit.Before;
@ -178,10 +180,11 @@ public class WalletTest extends TestWithWallet {
}
@Test
public void encryptDecryptWalletWithArbitraryPath() throws Exception {
public void encryptDecryptWalletWithArbitraryPathAndScriptType() throws Exception {
final byte[] ENTROPY = Sha256Hash.hash("don't use a string seed like this in real life".getBytes());
KeyChainGroup keyChainGroup = KeyChainGroup.builder(UNITTEST)
.addChain(DeterministicKeyChain.builder().seed(new DeterministicSeed(ENTROPY, "", 1389353062L))
.outputScriptType(Script.ScriptType.P2WPKH)
.accountPath(DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH).build())
.build();
Wallet encryptedWallet = new Wallet(UNITTEST, keyChainGroup);
@ -1618,7 +1621,8 @@ public class WalletTest extends TestWithWallet {
@Test
public void isWatching() {
assertFalse(wallet.isWatching());
Wallet watchingWallet = Wallet.fromWatchingKey(UNITTEST, wallet.getWatchingKey().dropPrivateBytes().dropParent());
Wallet watchingWallet = Wallet.fromWatchingKey(UNITTEST,
wallet.getWatchingKey().dropPrivateBytes().dropParent(), Script.ScriptType.P2PKH);
assertTrue(watchingWallet.isWatching());
wallet.encrypt(PASSWORD1);
assertFalse(wallet.isWatching());
@ -1630,7 +1634,8 @@ public class WalletTest extends TestWithWallet {
String serialized = watchKey.serializePubB58(UNITTEST);
// Construct watching wallet.
Wallet watchingWallet = Wallet.fromWatchingKey(UNITTEST, DeterministicKey.deserializeB58(null, serialized, UNITTEST));
Wallet watchingWallet = Wallet.fromWatchingKey(UNITTEST,
DeterministicKey.deserializeB58(null, serialized, UNITTEST), Script.ScriptType.P2PKH);
DeterministicKey key2 = watchingWallet.freshReceiveKey();
assertEquals(myKey, key2);
@ -2954,7 +2959,7 @@ public class WalletTest extends TestWithWallet {
assertEquals(THREE_CENTS.subtract(tx.getFee()), tx.getValueSentToMe(wallet));
// TX sends to one of our addresses (for now we ignore married wallets).
final Address toAddress = tx.getOutput(0).getScriptPubKey().getToAddress(UNITTEST);
final ECKey rotatingToKey = wallet.findKeyFromPubKeyHash(toAddress.getHash());
final ECKey rotatingToKey = wallet.findKeyFromPubKeyHash(toAddress.getHash(), toAddress.getOutputScriptType());
assertNotNull(rotatingToKey);
assertFalse(wallet.isKeyRotating(rotatingToKey));
assertEquals(3, tx.getInputs().size());
@ -2970,7 +2975,8 @@ public class WalletTest extends TestWithWallet {
sendMoneyToWallet(wallet, AbstractBlockChain.NewBlockType.BEST_CHAIN, CENT, LegacyAddress.fromKey(UNITTEST, key1));
wallet.doMaintenance(null, true);
tx = broadcaster.waitForTransactionAndSucceed();
assertNotNull(wallet.findKeyFromPubKeyHash(tx.getOutput(0).getScriptPubKey().getPubKeyHash()));
assertNotNull(wallet.findKeyFromPubKeyHash(tx.getOutput(0).getScriptPubKey().getPubKeyHash(),
toAddress.getOutputScriptType()));
log.info("Unexpected thing: {}", tx);
assertEquals(Coin.valueOf(19300), tx.getFee());
assertEquals(1, tx.getInputs().size());
@ -3055,7 +3061,7 @@ public class WalletTest extends TestWithWallet {
List<Transaction> txns = wallet.doMaintenance(null, false).get();
assertEquals(1, txns.size());
Address output = txns.get(0).getOutput(0).getScriptPubKey().getToAddress(UNITTEST);
ECKey usedKey = wallet.findKeyFromPubKeyHash(output.getHash());
ECKey usedKey = wallet.findKeyFromPubKeyHash(output.getHash(), output.getOutputScriptType());
assertEquals(goodKey.getCreationTimeSeconds(), usedKey.getCreationTimeSeconds());
assertEquals(goodKey.getCreationTimeSeconds(), wallet.freshReceiveKey().getCreationTimeSeconds());
assertEquals("mrM3TpCnav5YQuVA1xLercCGJH4DXujMtv", LegacyAddress.fromKey(UNITTEST, usedKey).toString());
@ -3129,7 +3135,8 @@ public class WalletTest extends TestWithWallet {
// Delete the sigs
for (TransactionInput input : req.tx.getInputs())
input.clearScriptBytes();
Wallet watching = Wallet.fromWatchingKey(UNITTEST, wallet.getWatchingKey().dropParent().dropPrivateBytes());
Wallet watching = Wallet.fromWatchingKey(UNITTEST, wallet.getWatchingKey().dropParent().dropPrivateBytes(),
Script.ScriptType.P2PKH);
watching.completeTx(SendRequest.forTx(req.tx));
}
@ -3531,4 +3538,39 @@ public class WalletTest extends TestWithWallet {
wallet = roundTrip(wallet);
assertTrue(wallet.isConsistent());
}
@Test
public void scriptTypeKeyChainRestrictions() {
// Set up chains: basic chain, P2PKH deterministric chain, P2WPKH deterministic chain.
DeterministicKeyChain p2pkhChain = DeterministicKeyChain.builder().random(new SecureRandom())
.outputScriptType(Script.ScriptType.P2PKH).build();
DeterministicKeyChain p2wpkhChain = DeterministicKeyChain.builder().random(new SecureRandom())
.outputScriptType(Script.ScriptType.P2WPKH).build();
KeyChainGroup kcg = KeyChainGroup.builder(UNITTEST).addChain(p2pkhChain).addChain(p2wpkhChain).build();
Wallet wallet = new Wallet(UNITTEST, kcg);
// Set up one key from each chain.
ECKey importedKey = new ECKey();
wallet.importKey(importedKey);
ECKey p2pkhKey = p2pkhChain.getKey(KeyPurpose.RECEIVE_FUNDS);
ECKey p2wpkhKey = p2wpkhChain.getKey(KeyPurpose.RECEIVE_FUNDS);
// Test imported key: it's not limited to script type.
assertTrue(wallet.isAddressMine(LegacyAddress.fromKey(UNITTEST, importedKey)));
assertTrue(wallet.isAddressMine(SegwitAddress.fromKey(UNITTEST, importedKey)));
assertEquals(importedKey, wallet.findKeyFromAddress(LegacyAddress.fromKey(UNITTEST, importedKey)));
assertEquals(importedKey, wallet.findKeyFromAddress(SegwitAddress.fromKey(UNITTEST, importedKey)));
// Test key from P2PKH chain: it's limited to P2PKH addresses
assertTrue(wallet.isAddressMine(LegacyAddress.fromKey(UNITTEST, p2pkhKey)));
assertFalse(wallet.isAddressMine(SegwitAddress.fromKey(UNITTEST, p2pkhKey)));
assertEquals(p2pkhKey, wallet.findKeyFromAddress(LegacyAddress.fromKey(UNITTEST, p2pkhKey)));
assertNull(wallet.findKeyFromAddress(SegwitAddress.fromKey(UNITTEST, p2pkhKey)));
// Test key from P2WPKH chain: it's limited to P2WPKH addresses
assertFalse(wallet.isAddressMine(LegacyAddress.fromKey(UNITTEST, p2wpkhKey)));
assertTrue(wallet.isAddressMine(SegwitAddress.fromKey(UNITTEST, p2wpkhKey)));
assertNull(wallet.findKeyFromAddress(LegacyAddress.fromKey(UNITTEST, p2wpkhKey)));
assertEquals(p2wpkhKey, wallet.findKeyFromAddress(SegwitAddress.fromKey(UNITTEST, p2wpkhKey)));
}
}

View file

@ -13,6 +13,7 @@ creation_timestamp: 1389353062000
deterministic_key {
chain_code: "XL\240FW\203\316\230\334\374J\003\357=\215\001\206\365\207Z\006m\334X`\236,;_\304\000^"
}
output_script_type: P2PKH
type: DETERMINISTIC_KEY
secret_bytes: "\370\\\267\361L\021K\214\215\272yRP\234(\304\365\303h\251\250\0236\270\344\210\300\330\363(=\332"

View file

@ -0,0 +1,280 @@
type: DETERMINISTIC_MNEMONIC
secret_bytes: "aerobic toe save section draw warm cute upon raccoon mother priority pilot taste sweet next traffic fatal sword dentist original crisp team caution rebel"
creation_timestamp: 1389353062000
deterministic_seed: "E\032\356\206\230,\275\263\364=\334^f\307\037\350\321X7R\262z\205\3564\371tp\2639R\342\027 J\266\253\250\320\022\031\233\271~O$\330\260\214\fz\231tI\353\215*\037\355\205\213.\224?"
account_path: 2147483649
type: DETERMINISTIC_KEY
secret_bytes: "\270E0\202(\362b\023\276\264\347\226E2\360\221\347\325\233L\203\3276\272\213\2436&\304\373\221\025"
public_key: "\002\342$\253\332\031\352\324q\316M\251}\274\267\370X$\366>Q\316\005\330\376\353f!WHLL\a"
creation_timestamp: 1389353062000
deterministic_key {
chain_code: "XL\240FW\203\316\230\334\374J\003\357=\215\001\206\365\207Z\006m\334X`\236,;_\304\000^"
}
output_script_type: P2WPKH
type: DETERMINISTIC_KEY
secret_bytes: "\267y\204N)\270Ysr\315?t\213.g\224\332\354#\262\216\341%\217B\322%v\365\357\301\202"
public_key: "\002\270\032\242\r\210\310\316\361\360s3\233A\374\002\r\035m;w~\032\201\237\367/O5\231\270\216k"
deterministic_key {
chain_code: "\255\v_(\027?\036\353\373\212\346\360\276\364q/\246\351z\331\332a\207\177&\335\206\220\276f+\211"
path: 2147483649
}
type: DETERMINISTIC_KEY
secret_bytes: "\322j5\033\375\257\371-\211~d)\"{\30629\3446\025=H\370Q\227\325=\002\240|r\343"
public_key: "\002\237\202\357O\355r\020(]\235\b\3162\263N\331\320\216\246\262\376\260\352\224\313Bw\347~\310\336X"
deterministic_key {
chain_code: "*\210c\235x\377\001\277\b\"\321\363\r*\'ci\310\317\344\220\025\341\v\r\337\034\200H\366\312P"
path: 2147483649
path: 0
issued_subkeys: 2
lookahead_size: 10
sigsRequiredToSpend: 1
}
type: DETERMINISTIC_KEY
secret_bytes: "(USw\200Yh\313F|o\213\240u\265@n\000r\033\305\376\211\a\304D\034\342\316\021\374\207"
public_key: "\002\031\335\304L?`\236\224c\252\305\375\2678\275\342\236\242\230\266C\363\327U\341\340\337GeW\2544"
deterministic_key {
chain_code: "\336m6\270\332/\363\016\340=;\003\225\341S\024\262\332vM\201L\207\342\306=!\352A\f\272{"
path: 2147483649
path: 1
issued_subkeys: 1
lookahead_size: 10
sigsRequiredToSpend: 1
}
type: DETERMINISTIC_KEY
public_key: "\002P\022\037\025\003\264\302/\321\314\243\246(\371\006\245\203\255\363o\237\250\b\233O?\225\316T)n\357"
deterministic_key {
chain_code: "9\032\260\204\177\362\362\310`\243)\2067\351\324O\355\337V\245\375\t\375\214mw\353\355\316\246bA"
path: 2147483649
path: 0
path: 0
}
type: DETERMINISTIC_KEY
public_key: "\003\246\306%*\023\234\0316\273;9\341\033\370 \341\220\312\216\003\300\316`\201\352\357\327\216\234\0254\031"
deterministic_key {
chain_code: "\335\267\305\177 \255ew\bH\210@\033\267\224f\375\325\205\362`\022.3\224\f\206\260\361z\260\371"
path: 2147483649
path: 0
path: 1
}
type: DETERMINISTIC_KEY
public_key: "\002\am\036\377\ff8\003\222\351#?\356e\000\367\254i0\324H~\251E/\352B\2616\0030+"
deterministic_key {
chain_code: "{#\231\t\034\023T@\317\322\344^w\254)\350!\327\221\033\263C\347\277zb\301^`\004\277\350"
path: 2147483649
path: 0
path: 2
}
type: DETERMINISTIC_KEY
public_key: "\003\307\214\031\332\032\205oH\346\272\v\267\n#9ivZ\247\327L)3\254\"\201\331K\340\211\026`"
deterministic_key {
chain_code: "@}\204\033\347\346\270\345\376M\027\263\262h\242\322[l\254\263\351O\334^\273k\003\225\2065\017\031"
path: 2147483649
path: 0
path: 3
}
type: DETERMINISTIC_KEY
public_key: "\002\025N\\\177\273\325\224\353{5G\273\304J\234t\354\260f\036!L\202\246\361\223\356T.\200\223\206"
deterministic_key {
chain_code: "\230\244\354A\316\272\223j\226\341\023-\327\302>\317\275\022\252\366R\230\312a\310\347u,\004\233\b9"
path: 2147483649
path: 0
path: 4
}
type: DETERMINISTIC_KEY
public_key: "\002\b\035p\217z\250\".;C\0313>\366\251\"\246Y\307\034\265\273V\202P\207\024\322\316\340$7"
deterministic_key {
chain_code: "W&\026N5\205:1\216d\256m\357\2248\341\227\204\352\177P\265\"\036\v\204p1\177BC\236"
path: 2147483649
path: 0
path: 5
}
type: DETERMINISTIC_KEY
public_key: "\002\220L\270j\r\270\277\336\016\370\332\203\260\356\322\263<\357$\003\017k\nU<6\235\n\253\230;\205"
deterministic_key {
chain_code: "\025\304d\363\376\aA\275p&_\336\355\374\224\315\264\222m\363m\222S\200\017-\274\330\322\244A\302"
path: 2147483649
path: 0
path: 6
}
type: DETERMINISTIC_KEY
public_key: "\003\232\370\302\261\006\200\347-\257[\205\v\273\352\v8_\270\304\331Z.Ud\351\264s\251*8_\375"
deterministic_key {
chain_code: "0\345\362I\317=\033I\270\b\322J\316\275D\220\335\334\320\367\275\334\336\367\237l\377\376\3638\203\233"
path: 2147483649
path: 0
path: 7
}
type: DETERMINISTIC_KEY
public_key: "\002E\317\324\302\345\b\021\275qQ\303\030\257-\352\2744>H\026\340\371\022\"X\025\255\357\367\271\316D"
deterministic_key {
chain_code: "\313\"m\227\025\343o\2252\377\320\200FF~T\322\363\2330\021\251 \276\317\274\0227\322\a96"
path: 2147483649
path: 0
path: 8
}
type: DETERMINISTIC_KEY
public_key: "\003\376k!\002\320\037\352\030\211?\031/H*\242\326\365\022\374\273\254\323)3\370\1770\340nv\333\244"
deterministic_key {
chain_code: "\315u1\361c\030\266@+\344\225\022\337\034\325\272\223r\251\262\357\211\255.\356\005\031Ds\217,\367"
path: 2147483649
path: 0
path: 9
}
type: DETERMINISTIC_KEY
public_key: "\002\003O\356\2103\362\253-\016\211\276\023i\233\256\3044\236RP<\253\367\346.R/\330\006\331\017\n"
deterministic_key {
chain_code: "\231\026I\a1\'\350^]THS\323\355\v\347\237\356\302\226\343\216K,omM\237\272\373\234\207"
path: 2147483649
path: 0
path: 10
}
type: DETERMINISTIC_KEY
public_key: "\0035\304\365\255c\376\260\366\213l\246o\v\342\300\232f\301\306\233\245\373Z\300\230\231\206\201L\231\235\306"
deterministic_key {
chain_code: "\256\216Pe\026\317\313\a\376\031\021\302\207&\363N\271\'\357\275|\334\315\215\272\237\202\231\277\370\264\356"
path: 2147483649
path: 0
path: 11
}
type: DETERMINISTIC_KEY
public_key: "\002g\340hs\337]\302$\275\225\277\326\233\3016`\027\267Y+\323\376\360\353\266\350X\247\320\273\247\026"
deterministic_key {
chain_code: " \217\242P&\336p\226W\200I+\346\t\213\347d\260-\243\241+\213+\304\365^\227\203\206\231\031"
path: 2147483649
path: 0
path: 12
}
type: DETERMINISTIC_KEY
public_key: "\002\036\220\032\376\211\327G\236\371\032\276\004l\341\206\235\001\336\2248\314\027\226u*j\251\262c\252\346\265"
deterministic_key {
chain_code: "\363\345\031D\by@\353zGS\267o{ c`c\300NH\006T%miB\210\325x$\306"
path: 2147483649
path: 1
path: 0
}
type: DETERMINISTIC_KEY
public_key: "\00269\201\317\302t\016Q\247\205n\204\263MI\345X\330da\006>\251\204h\372+u.>\321v"
deterministic_key {
chain_code: "\317j\363\36773\254u\037\3663\243\n-Z\237T\210\263\202u\315\337/\245.H\364\004\253+o"
path: 2147483649
path: 1
path: 1
}
type: DETERMINISTIC_KEY
public_key: "\002\025#\214(\322\374C\000\234KO\1776\276T\354\217\372g:\340\035c\271\275\375RF`\357\260u"
deterministic_key {
chain_code: "\351\345 \224\354I\201\254\275\207\300\357\240\216\326P\031f\232Ll@\363\207\236\203\027t\350\370Kn"
path: 2147483649
path: 1
path: 2
}
type: DETERMINISTIC_KEY
public_key: "\002\250+_^\241\353Z%ie\237\233\241.#\322\"\373W\276eJ\255\327\250\024v\016\0340\343\t"
deterministic_key {
chain_code: "\211\220?,\212\350\001\233[><h\"\246{\311\372ET\353n\022\332\224\264\373\b\023\272\277$\325"
path: 2147483649
path: 1
path: 3
}
type: DETERMINISTIC_KEY
public_key: "\002\323\260\005\254\242\350\307\3556\302\373\003\246\330\000g`+0\253\365Q\323Zz\"?\376G.\356f"
deterministic_key {
chain_code: "\273\016\342\\V\200w\252\203\350B\314\277\245;u\327\372wL\311b_S+\376\277\306\375k\" "
path: 2147483649
path: 1
path: 4
}
type: DETERMINISTIC_KEY
public_key: "\0032\331b?\"\026\337\235\026u^\331\327-j4p\357\177\247c\355\310(9\330\231\262\023& \227"
deterministic_key {
chain_code: "\272\a\311\264\237\246\312G\v~\005\300\236\214ZHk\t\301\203@%iP\005\204\367\344\334\302`\r"
path: 2147483649
path: 1
path: 5
}
type: DETERMINISTIC_KEY
public_key: "\003\377\317\372\345\267\264\024\343\302>\f\202~&\034\376\206\360\006\314\035\243\261#*r@\"\037\275\004H"
deterministic_key {
chain_code: "\335\030j\262\306\024\fg\346n\025\254\322U\331\323\270\004\017\023\240>\260\307_\343\235\304\222L\212\372"
path: 2147483649
path: 1
path: 6
}
type: DETERMINISTIC_KEY
public_key: "\003!)\246\275\r\201\246\\\233\260\345\222\215v\371g\314\211\n\0375\203m\305\001`\177 zu4O"
deterministic_key {
chain_code: "\333h\304\004\366\346\351*\366\205\236\377L_\200a\354N\241\235B\272\\\004|\245=\266\324W\352\'"
path: 2147483649
path: 1
path: 7
}
type: DETERMINISTIC_KEY
public_key: "\003\026o\366\273n\366\371Krn\335\216\246\022T\005\f\265t\377\036\217.\\\bV!\fs\251\365\243"
deterministic_key {
chain_code: "\305\257\344_\027\025\033=>\000 \tm\273\":\265\214\023\267\203me\264*P\222\2247+Ow"
path: 2147483649
path: 1
path: 8
}
type: DETERMINISTIC_KEY
public_key: "\003\345M0`s\370\3617\n\254\'H<\301}n\326\374\253\240\273wT\t\201r|*\331\361\315\277"
deterministic_key {
chain_code: "V\310\t\226\301\254\f\230\024\332\341a\271hG\331\232\024\266\235\377\004\021\212\016\257\227V\342c\212\203"
path: 2147483649
path: 1
path: 9
}
type: DETERMINISTIC_KEY
public_key: "\002|h\274DS-\330\035\366o\203x\3343\020q\225\024\005c\326B\200q2\213f\346N?V\330"
deterministic_key {
chain_code: "\233\211\254E\347\036\352\262\027\363WF\324\345\232\247j\277\f\344\031\\\177\263\223j\333?\003\025W\020"
path: 2147483649
path: 1
path: 10
}
type: DETERMINISTIC_KEY
public_key: "\002\256[\025\037L\371\365\300\357\315\224v\006\r\236\335BIO\210vvT\n\266\243{G\321`\366\377"
deterministic_key {
chain_code: ":\240\372\237\034\033DR1\220\t\022\200\033\307/\200wBy\365\223\244\0170\327\277>Z9\365\277"
path: 2147483649
path: 1
path: 11
}
type: DETERMINISTIC_KEY
public_key: "\002\375\023ByLoCf\260\230\001\334\373\031\327\325\347\353Ad\253\244\246A=\217vf\034D5\250"
deterministic_key {
chain_code: "\277\265\332jOb\260G6,u\342\263A]\214KF\360\337\217\231:\342x\225\330\250Fa*n"
path: 2147483649
path: 1
path: 12
}

View file

@ -11,6 +11,7 @@ creation_timestamp: 1389353062000
deterministic_key {
chain_code: "XL\240FW\203\316\230\334\374J\003\357=\215\001\206\365\207Z\006m\334X`\236,;_\304\000^"
}
output_script_type: P2PKH
type: DETERMINISTIC_KEY
secret_bytes: "\354B\331\275;\000\254?\3428\006\220G\365\243\333s\260s\213R\313\307\377f\331B\351\327=\001\333"

View file

@ -6,6 +6,7 @@ deterministic_key {
chain_code: "_\377Y\344\324\234\263\356_\002\325\222\241B\307C\302\021L\345f\2731u\364~\325\306\333\367\233\257"
path: 2147483650
}
output_script_type: P2PKH
type: DETERMINISTIC_KEY
secret_bytes: "\214E-\031jfj\351\324\206;\304\034v93\035[\251\374\t_@\262\273%q.r\276\016\241"

View file

@ -8,6 +8,7 @@ deterministic_key {
path: 2147483649
path: 2147483648
}
output_script_type: P2PKH
type: DETERMINISTIC_KEY
secret_bytes: "\362\305\242\3637\2748Z?]\035s\272\253J\300\033\250\022r\350\020\277U\036K<\335\237\333/\303"

View file

@ -6,6 +6,7 @@ deterministic_key {
chain_code: "F\336\2067\377M\026)%\357ZL#\203\320\324\217-\3305\310\244\n\205\277E\323L\250ww\314"
path: 2147483648
}
output_script_type: P2PKH
type: DETERMINISTIC_KEY
secret_bytes: "\362\305\242\3637\2748Z?]\035s\272\253J\300\033\250\022r\350\020\277U\036K<\335\237\333/\303"

View file

@ -6,6 +6,7 @@ deterministic_key {
chain_code: "\370\017\223\021O?.@gZ|\233j\3437\317q-\241!\177J \323\'\264s\203\314\321\v\346"
path: 2147483648
}
output_script_type: P2PKH
type: DETERMINISTIC_KEY
secret_bytes: "<M\020I\364\276\336Z\255\341\330\257\337 \366E_\027\2433w\325\263\"$\350\f\244\006\251u\021"

View file

@ -7,6 +7,7 @@ deterministic_key {
path: 2147483649
path: 2147483648
}
output_script_type: P2PKH
type: DETERMINISTIC_KEY
public_key: "\003\2471\326i\331A\337|\373\276\3214\257\363\266Q\315x\341\317\200\243\234\336<s}\261\240,\233\371"

View file

@ -0,0 +1,264 @@
type: DETERMINISTIC_KEY
public_key: "\002\270\032\242\r\210\310\316\361\360s3\233A\374\002\r\035m;w~\032\201\237\367/O5\231\270\216k"
creation_timestamp: 100000000
deterministic_key {
chain_code: "\255\v_(\027?\036\353\373\212\346\360\276\364q/\246\351z\331\332a\207\177&\335\206\220\276f+\211"
path: 2147483649
}
output_script_type: P2WPKH
type: DETERMINISTIC_KEY
public_key: "\002\237\202\357O\355r\020(]\235\b\3162\263N\331\320\216\246\262\376\260\352\224\313Bw\347~\310\336X"
deterministic_key {
chain_code: "*\210c\235x\377\001\277\b\"\321\363\r*\'ci\310\317\344\220\025\341\v\r\337\034\200H\366\312P"
path: 2147483649
path: 0
issued_subkeys: 2
lookahead_size: 10
sigsRequiredToSpend: 1
}
type: DETERMINISTIC_KEY
public_key: "\002\031\335\304L?`\236\224c\252\305\375\2678\275\342\236\242\230\266C\363\327U\341\340\337GeW\2544"
deterministic_key {
chain_code: "\336m6\270\332/\363\016\340=;\003\225\341S\024\262\332vM\201L\207\342\306=!\352A\f\272{"
path: 2147483649
path: 1
issued_subkeys: 1
lookahead_size: 10
sigsRequiredToSpend: 1
}
type: DETERMINISTIC_KEY
public_key: "\002P\022\037\025\003\264\302/\321\314\243\246(\371\006\245\203\255\363o\237\250\b\233O?\225\316T)n\357"
deterministic_key {
chain_code: "9\032\260\204\177\362\362\310`\243)\2067\351\324O\355\337V\245\375\t\375\214mw\353\355\316\246bA"
path: 2147483649
path: 0
path: 0
}
type: DETERMINISTIC_KEY
public_key: "\003\246\306%*\023\234\0316\273;9\341\033\370 \341\220\312\216\003\300\316`\201\352\357\327\216\234\0254\031"
deterministic_key {
chain_code: "\335\267\305\177 \255ew\bH\210@\033\267\224f\375\325\205\362`\022.3\224\f\206\260\361z\260\371"
path: 2147483649
path: 0
path: 1
}
type: DETERMINISTIC_KEY
public_key: "\002\am\036\377\ff8\003\222\351#?\356e\000\367\254i0\324H~\251E/\352B\2616\0030+"
deterministic_key {
chain_code: "{#\231\t\034\023T@\317\322\344^w\254)\350!\327\221\033\263C\347\277zb\301^`\004\277\350"
path: 2147483649
path: 0
path: 2
}
type: DETERMINISTIC_KEY
public_key: "\003\307\214\031\332\032\205oH\346\272\v\267\n#9ivZ\247\327L)3\254\"\201\331K\340\211\026`"
deterministic_key {
chain_code: "@}\204\033\347\346\270\345\376M\027\263\262h\242\322[l\254\263\351O\334^\273k\003\225\2065\017\031"
path: 2147483649
path: 0
path: 3
}
type: DETERMINISTIC_KEY
public_key: "\002\025N\\\177\273\325\224\353{5G\273\304J\234t\354\260f\036!L\202\246\361\223\356T.\200\223\206"
deterministic_key {
chain_code: "\230\244\354A\316\272\223j\226\341\023-\327\302>\317\275\022\252\366R\230\312a\310\347u,\004\233\b9"
path: 2147483649
path: 0
path: 4
}
type: DETERMINISTIC_KEY
public_key: "\002\b\035p\217z\250\".;C\0313>\366\251\"\246Y\307\034\265\273V\202P\207\024\322\316\340$7"
deterministic_key {
chain_code: "W&\026N5\205:1\216d\256m\357\2248\341\227\204\352\177P\265\"\036\v\204p1\177BC\236"
path: 2147483649
path: 0
path: 5
}
type: DETERMINISTIC_KEY
public_key: "\002\220L\270j\r\270\277\336\016\370\332\203\260\356\322\263<\357$\003\017k\nU<6\235\n\253\230;\205"
deterministic_key {
chain_code: "\025\304d\363\376\aA\275p&_\336\355\374\224\315\264\222m\363m\222S\200\017-\274\330\322\244A\302"
path: 2147483649
path: 0
path: 6
}
type: DETERMINISTIC_KEY
public_key: "\003\232\370\302\261\006\200\347-\257[\205\v\273\352\v8_\270\304\331Z.Ud\351\264s\251*8_\375"
deterministic_key {
chain_code: "0\345\362I\317=\033I\270\b\322J\316\275D\220\335\334\320\367\275\334\336\367\237l\377\376\3638\203\233"
path: 2147483649
path: 0
path: 7
}
type: DETERMINISTIC_KEY
public_key: "\002E\317\324\302\345\b\021\275qQ\303\030\257-\352\2744>H\026\340\371\022\"X\025\255\357\367\271\316D"
deterministic_key {
chain_code: "\313\"m\227\025\343o\2252\377\320\200FF~T\322\363\2330\021\251 \276\317\274\0227\322\a96"
path: 2147483649
path: 0
path: 8
}
type: DETERMINISTIC_KEY
public_key: "\003\376k!\002\320\037\352\030\211?\031/H*\242\326\365\022\374\273\254\323)3\370\1770\340nv\333\244"
deterministic_key {
chain_code: "\315u1\361c\030\266@+\344\225\022\337\034\325\272\223r\251\262\357\211\255.\356\005\031Ds\217,\367"
path: 2147483649
path: 0
path: 9
}
type: DETERMINISTIC_KEY
public_key: "\002\003O\356\2103\362\253-\016\211\276\023i\233\256\3044\236RP<\253\367\346.R/\330\006\331\017\n"
deterministic_key {
chain_code: "\231\026I\a1\'\350^]THS\323\355\v\347\237\356\302\226\343\216K,omM\237\272\373\234\207"
path: 2147483649
path: 0
path: 10
}
type: DETERMINISTIC_KEY
public_key: "\0035\304\365\255c\376\260\366\213l\246o\v\342\300\232f\301\306\233\245\373Z\300\230\231\206\201L\231\235\306"
deterministic_key {
chain_code: "\256\216Pe\026\317\313\a\376\031\021\302\207&\363N\271\'\357\275|\334\315\215\272\237\202\231\277\370\264\356"
path: 2147483649
path: 0
path: 11
}
type: DETERMINISTIC_KEY
public_key: "\002g\340hs\337]\302$\275\225\277\326\233\3016`\027\267Y+\323\376\360\353\266\350X\247\320\273\247\026"
deterministic_key {
chain_code: " \217\242P&\336p\226W\200I+\346\t\213\347d\260-\243\241+\213+\304\365^\227\203\206\231\031"
path: 2147483649
path: 0
path: 12
}
type: DETERMINISTIC_KEY
public_key: "\002\036\220\032\376\211\327G\236\371\032\276\004l\341\206\235\001\336\2248\314\027\226u*j\251\262c\252\346\265"
deterministic_key {
chain_code: "\363\345\031D\by@\353zGS\267o{ c`c\300NH\006T%miB\210\325x$\306"
path: 2147483649
path: 1
path: 0
}
type: DETERMINISTIC_KEY
public_key: "\00269\201\317\302t\016Q\247\205n\204\263MI\345X\330da\006>\251\204h\372+u.>\321v"
deterministic_key {
chain_code: "\317j\363\36773\254u\037\3663\243\n-Z\237T\210\263\202u\315\337/\245.H\364\004\253+o"
path: 2147483649
path: 1
path: 1
}
type: DETERMINISTIC_KEY
public_key: "\002\025#\214(\322\374C\000\234KO\1776\276T\354\217\372g:\340\035c\271\275\375RF`\357\260u"
deterministic_key {
chain_code: "\351\345 \224\354I\201\254\275\207\300\357\240\216\326P\031f\232Ll@\363\207\236\203\027t\350\370Kn"
path: 2147483649
path: 1
path: 2
}
type: DETERMINISTIC_KEY
public_key: "\002\250+_^\241\353Z%ie\237\233\241.#\322\"\373W\276eJ\255\327\250\024v\016\0340\343\t"
deterministic_key {
chain_code: "\211\220?,\212\350\001\233[><h\"\246{\311\372ET\353n\022\332\224\264\373\b\023\272\277$\325"
path: 2147483649
path: 1
path: 3
}
type: DETERMINISTIC_KEY
public_key: "\002\323\260\005\254\242\350\307\3556\302\373\003\246\330\000g`+0\253\365Q\323Zz\"?\376G.\356f"
deterministic_key {
chain_code: "\273\016\342\\V\200w\252\203\350B\314\277\245;u\327\372wL\311b_S+\376\277\306\375k\" "
path: 2147483649
path: 1
path: 4
}
type: DETERMINISTIC_KEY
public_key: "\0032\331b?\"\026\337\235\026u^\331\327-j4p\357\177\247c\355\310(9\330\231\262\023& \227"
deterministic_key {
chain_code: "\272\a\311\264\237\246\312G\v~\005\300\236\214ZHk\t\301\203@%iP\005\204\367\344\334\302`\r"
path: 2147483649
path: 1
path: 5
}
type: DETERMINISTIC_KEY
public_key: "\003\377\317\372\345\267\264\024\343\302>\f\202~&\034\376\206\360\006\314\035\243\261#*r@\"\037\275\004H"
deterministic_key {
chain_code: "\335\030j\262\306\024\fg\346n\025\254\322U\331\323\270\004\017\023\240>\260\307_\343\235\304\222L\212\372"
path: 2147483649
path: 1
path: 6
}
type: DETERMINISTIC_KEY
public_key: "\003!)\246\275\r\201\246\\\233\260\345\222\215v\371g\314\211\n\0375\203m\305\001`\177 zu4O"
deterministic_key {
chain_code: "\333h\304\004\366\346\351*\366\205\236\377L_\200a\354N\241\235B\272\\\004|\245=\266\324W\352\'"
path: 2147483649
path: 1
path: 7
}
type: DETERMINISTIC_KEY
public_key: "\003\026o\366\273n\366\371Krn\335\216\246\022T\005\f\265t\377\036\217.\\\bV!\fs\251\365\243"
deterministic_key {
chain_code: "\305\257\344_\027\025\033=>\000 \tm\273\":\265\214\023\267\203me\264*P\222\2247+Ow"
path: 2147483649
path: 1
path: 8
}
type: DETERMINISTIC_KEY
public_key: "\003\345M0`s\370\3617\n\254\'H<\301}n\326\374\253\240\273wT\t\201r|*\331\361\315\277"
deterministic_key {
chain_code: "V\310\t\226\301\254\f\230\024\332\341a\271hG\331\232\024\266\235\377\004\021\212\016\257\227V\342c\212\203"
path: 2147483649
path: 1
path: 9
}
type: DETERMINISTIC_KEY
public_key: "\002|h\274DS-\330\035\366o\203x\3343\020q\225\024\005c\326B\200q2\213f\346N?V\330"
deterministic_key {
chain_code: "\233\211\254E\347\036\352\262\027\363WF\324\345\232\247j\277\f\344\031\\\177\263\223j\333?\003\025W\020"
path: 2147483649
path: 1
path: 10
}
type: DETERMINISTIC_KEY
public_key: "\002\256[\025\037L\371\365\300\357\315\224v\006\r\236\335BIO\210vvT\n\266\243{G\321`\366\377"
deterministic_key {
chain_code: ":\240\372\237\034\033DR1\220\t\022\200\033\307/\200wBy\365\223\244\0170\327\277>Z9\365\277"
path: 2147483649
path: 1
path: 11
}
type: DETERMINISTIC_KEY
public_key: "\002\375\023ByLoCf\260\230\001\334\373\031\327\325\347\353Ad\253\244\246A=\217vf\034D5\250"
deterministic_key {
chain_code: "\277\265\332jOb\260G6,u\342\263A]\214KF\360\337\217\231:\342x\225\330\250Fa*n"
path: 2147483649
path: 1
path: 12
}

View file

@ -5,6 +5,7 @@ deterministic_key {
chain_code: "SE\031\000\277\023 \256W\237\036\034c\340\aB-I\246\304\025\325\262\314\235\240]\020\374T\315\027"
path: 1
}
output_script_type: P2PKH
type: DETERMINISTIC_KEY
public_key: "\003QO{n\351\326\357\343T\203u\345\025{]\231\234\273A\376U`\307\212\336h\255\215\335(\364\001"

View file

@ -5,6 +5,7 @@ deterministic_key {
chain_code: "\370\017\223\021O?.@gZ|\233j\3437\317q-\241!\177J \323\'\264s\203\314\321\v\346"
path: 2147483648
}
output_script_type: P2PKH
type: DETERMINISTIC_KEY
public_key: "\002\361V\216\001\371p\270\212\272\236%\216\356o\025g\r\035>a\305j\001P\217Q\242\261.\353\367\315"

View file

@ -1,5 +1,6 @@
/*
* Copyright 2015 Ross Nicoll.
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -64,7 +65,7 @@ public class GenerateLowSTests {
final ECKey key = new ECKey(secureRandom);
final KeyBag bag = new KeyBag() {
@Override
public ECKey findKeyFromPubKeyHash(byte[] pubKeyHash) {
public ECKey findKeyFromPubKeyHash(byte[] pubkeyHash, Script.ScriptType scriptType) {
return key;
}

View file

@ -20,8 +20,10 @@ import org.bitcoinj.core.listeners.DownloadProgressTracker;
import org.bitcoinj.core.*;
import org.bitcoinj.net.discovery.DnsDiscovery;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.script.Script;
import org.bitcoinj.store.SPVBlockStore;
import org.bitcoinj.wallet.DeterministicSeed;
import org.bitcoinj.wallet.KeyChainGroupStructure;
import org.bitcoinj.wallet.Wallet;
import java.io.File;
@ -46,7 +48,7 @@ public class RestoreFromSeed {
DeterministicSeed seed = new DeterministicSeed(seedCode, null, passphrase, creationtime);
// The wallet class provides a easy fromSeed() function that loads a new wallet from a given seed.
Wallet wallet = Wallet.fromSeed(params, seed);
Wallet wallet = Wallet.fromSeed(params, seed, Script.ScriptType.P2PKH);
// Because we are importing an existing wallet which might already have transactions we must re-download the blockchain to make the wallet picks up these transactions
// You can find some information about this in the guides: https://bitcoinj.github.io/working-with-the-wallet#setup

View file

@ -24,6 +24,8 @@ import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.protocols.payments.PaymentProtocol;
import org.bitcoinj.protocols.payments.PaymentProtocolException;
import org.bitcoinj.protocols.payments.PaymentSession;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.Script.ScriptType;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptException;
import org.bitcoinj.script.ScriptPattern;
@ -65,6 +67,7 @@ import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Peer;
import org.bitcoinj.core.PeerAddress;
import org.bitcoinj.core.PeerGroup;
import org.bitcoinj.core.SegwitAddress;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
@ -121,6 +124,7 @@ public class WalletTool {
private static OptionSpec<Date> dateFlag;
private static OptionSpec<Long> unixtimeFlag;
private static OptionSpec<String> seedFlag, watchFlag;
private static OptionSpec<Script.ScriptType> outputScriptTypeFlag;
private static OptionSpec<String> xpubkeysFlag;
private static NetworkParameters params;
@ -233,6 +237,7 @@ public class WalletTool {
OptionSpec<String> walletFileName = parser.accepts("wallet").withRequiredArg().defaultsTo("wallet");
seedFlag = parser.accepts("seed").withRequiredArg();
watchFlag = parser.accepts("watchkey").withRequiredArg();
outputScriptTypeFlag = parser.accepts("output-script-type").withRequiredArg().ofType(Script.ScriptType.class);
OptionSpec<NetworkEnum> netFlag = parser.accepts("net").withRequiredArg().ofType(NetworkEnum.class).defaultsTo(NetworkEnum.MAIN);
dateFlag = parser.accepts("date").withRequiredArg().ofType(Date.class)
.withValuesConvertedBy(DateConverter.datePattern("yyyy/MM/dd"));
@ -1313,6 +1318,7 @@ public class WalletTool {
return;
}
long creationTimeSecs = getCreationTimeSeconds();
ScriptType outputScriptType = options.valueOf(outputScriptTypeFlag);
if (creationTimeSecs == 0)
creationTimeSecs = MnemonicCode.BIP39_STANDARDISATION_TIME_SECS;
if (options.has(seedFlag)) {
@ -1337,11 +1343,11 @@ public class WalletTool {
// not reached - all subclasses handled above
throw new RuntimeException(e);
}
wallet = Wallet.fromSeed(params, seed);
wallet = Wallet.fromSeed(params, seed, outputScriptType);
} else if (options.has(watchFlag)) {
wallet = Wallet.fromWatchingKeyB58(params, options.valueOf(watchFlag), creationTimeSecs);
} else {
wallet = new Wallet(params);
wallet = Wallet.createDeterministic(params, outputScriptType);
}
if (password != null)
wallet.encrypt(password);
@ -1448,7 +1454,10 @@ public class WalletTool {
if (!key.isCompressed())
System.out.println("WARNING: Importing an uncompressed key");
wallet.importKey(key);
System.out.println(LegacyAddress.fromKey(params, key) + " " + key);
System.out.print("Addresses: " + LegacyAddress.fromKey(params, key));
if (key.isCompressed())
System.out.print("," + SegwitAddress.fromKey(params, key));
System.out.println();
}
/**
@ -1489,7 +1498,7 @@ public class WalletTool {
key = wallet.findKeyFromPubKey(HEX.decode(pubKey));
} else {
try {
Address address = LegacyAddress.fromBase58(wallet.getParams(), addr);
Address address = Address.fromString(wallet.getParams(), addr);
key = wallet.findKeyFromAddress(address);
} catch (AddressFormatException e) {
System.err.println(addr + " does not parse as a Bitcoin address of the right network parameters.");
@ -1508,8 +1517,8 @@ public class WalletTool {
}
private static void currentReceiveAddr() {
ECKey key = wallet.currentReceiveKey();
System.out.println(LegacyAddress.fromKey(params, key) + " " + key);
Address address = wallet.currentReceiveAddress();
System.out.println(address);
}
private static void dumpWallet() throws BlockStoreException {

View file

@ -12,7 +12,8 @@ Usage: wallet-tool --flags action-name
Will complain and require --force if the wallet already exists.
If --seed is present, it should specify either a mnemonic code or hex/base58 raw seed bytes.
If --watchkey is present, it creates a watching wallet using the specified base58 xpub.
If --seed or --watchkey is combined with either --date or --unixtime, use that as a birthdate for
If --seed or --watchkey is combined with either --date or --unixtime, use that as a birthdate for.
If --output-script-type, use that for deriving addresses.
the wallet. See the set-creation-time action for the meaning of these flags.
marry Makes the wallet married with other parties, requiring multisig to spend funds.
External public keys for other signing parties must be specified with --xpubkeys (comma separated).

View file

@ -22,6 +22,7 @@ import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Utils;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.params.*;
import org.bitcoinj.script.Script;
import org.bitcoinj.utils.BriefLogFormatter;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.DeterministicSeed;
@ -46,6 +47,7 @@ import static wallettemplate.utils.GuiUtils.*;
public class Main extends Application {
public static NetworkParameters params = MainNetParams.get();
public static final Script.ScriptType PREFERRED_OUTPUT_SCRIPT_TYPE = Script.ScriptType.P2WPKH;
public static final String APP_NAME = "WalletTemplate";
private static final String WALLET_FILE_NAME = APP_NAME.replaceAll("[^a-zA-Z0-9.-]", "_") + "-"
+ params.getPaymentProtocolId();
@ -132,7 +134,7 @@ public class Main extends Application {
public void setupWalletKit(@Nullable DeterministicSeed seed) {
// If seed is non-null it means we are restoring from backup.
bitcoin = new WalletAppKit(params, new File("."), WALLET_FILE_NAME) {
bitcoin = new WalletAppKit(params, PREFERRED_OUTPUT_SCRIPT_TYPE, null, new File("."), WALLET_FILE_NAME) {
@Override
protected void onSetupCompleted() {
// Don't make the user wait for confirmations for now, as the intention is they're sending it

View file

@ -58,6 +58,7 @@ public class SendMoneyController {
new TextFieldValidator(amountEdit, text ->
!WTUtils.didThrow(() -> checkState(Coin.parseCoin(text).compareTo(balance) <= 0)));
amountEdit.setText(balance.toPlainString());
address.setPromptText(Address.fromKey(Main.params, new ECKey(), Main.PREFERRED_OUTPUT_SCRIPT_TYPE).toString());
}
public void cancel(ActionEvent event) {

View file

@ -33,7 +33,7 @@
<Font size="24.0" />
</font>
</Label>
<TextField fx:id="address" maxWidth="1.7976931348623157E308" promptText="1EZEqFBd8yuc9ir2761987q7k3VcALC8YQ" HBox.hgrow="ALWAYS">
<TextField fx:id="address" maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS">
<VBox.margin>
<Insets />
</VBox.margin>