diff --git a/core/src/main/java/org/bitcoinj/core/Address.java b/core/src/main/java/org/bitcoinj/core/Address.java
index db6342fe2..b28cd3d90 100644
--- a/core/src/main/java/org/bitcoinj/core/Address.java
+++ b/core/src/main/java/org/bitcoinj/core/Address.java
@@ -80,8 +80,8 @@ public static Address fromString(@Nullable NetworkParameters params, String str)
* @return constructed address
*/
public static Address fromKey(final NetworkParameters params, final ECKey key, final ScriptType outputScriptType) {
- if (outputScriptType == Script.ScriptType.P2PKH)
- return LegacyAddress.fromKey(params, key);
+ if (outputScriptType == Script.ScriptType.P2PKH || outputScriptType == ScriptType.P2SH_P2WPKH)
+ return LegacyAddress.fromKey(params, key, outputScriptType);
else if (outputScriptType == Script.ScriptType.P2WPKH)
return SegwitAddress.fromKey(params, key);
else
diff --git a/core/src/main/java/org/bitcoinj/core/ECKey.java b/core/src/main/java/org/bitcoinj/core/ECKey.java
index 03319a8a3..9a802abfd 100644
--- a/core/src/main/java/org/bitcoinj/core/ECKey.java
+++ b/core/src/main/java/org/bitcoinj/core/ECKey.java
@@ -1295,7 +1295,7 @@ public void formatKeyWithAddress(boolean includePrivateKeys, @Nullable KeyParame
if (outputScriptType != null) {
builder.append(Address.fromKey(params, this, outputScriptType));
} else {
- builder.append(LegacyAddress.fromKey(params, this));
+ builder.append(LegacyAddress.fromKey(params, this, Script.ScriptType.P2PKH));
if (isCompressed())
builder.append(',').append(SegwitAddress.fromKey(params, this));
}
diff --git a/core/src/main/java/org/bitcoinj/core/LegacyAddress.java b/core/src/main/java/org/bitcoinj/core/LegacyAddress.java
index 821231adc..f404ad321 100644
--- a/core/src/main/java/org/bitcoinj/core/LegacyAddress.java
+++ b/core/src/main/java/org/bitcoinj/core/LegacyAddress.java
@@ -25,7 +25,10 @@
import com.google.common.primitives.UnsignedBytes;
import org.bitcoinj.params.Networks;
+import org.bitcoinj.script.Script;
import org.bitcoinj.script.Script.ScriptType;
+import org.bitcoinj.script.ScriptBuilder;
+import org.bitcoinj.script.ScriptPattern;
/**
*
A Bitcoin address looks like 1MsScoe2fTJoq4ZPdQgqyhgWeoNamYPevy and is derived from an elliptic curve public key
@@ -50,7 +53,7 @@ public class LegacyAddress extends Address {
/**
* Private constructor. Use {@link #fromBase58(NetworkParameters, String)},
* {@link #fromPubKeyHash(NetworkParameters, byte[])}, {@link #fromScriptHash(NetworkParameters, byte[])} or
- * {@link #fromKey(NetworkParameters, ECKey)}.
+ * {@link #fromKey(NetworkParameters, ECKey, ScriptType)}.
*
* @param params
* network this address is valid for
@@ -91,8 +94,15 @@ public static LegacyAddress fromPubKeyHash(NetworkParameters params, byte[] hash
* only the public part is used
* @return constructed address
*/
- public static LegacyAddress fromKey(NetworkParameters params, ECKey key) {
- return fromPubKeyHash(params, key.getPubKeyHash());
+ public static LegacyAddress fromKey(NetworkParameters params, ECKey key, ScriptType outputScriptType) {
+ if(outputScriptType == ScriptType.P2PKH) {
+ return fromPubKeyHash(params, key.getPubKeyHash());
+ } else if(outputScriptType == ScriptType.P2SH_P2WPKH) {
+ Script script = ScriptBuilder.createP2SHP2WPKHOutputScript(key);
+ return fromScriptHash(params, ScriptPattern.extractHashFromP2SH(script));
+ } else {
+ throw new IllegalArgumentException("Prohibited output script type: " + outputScriptType);
+ }
}
/**
diff --git a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java
index 4bcdb3f73..9a804e3ab 100644
--- a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java
+++ b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java
@@ -79,6 +79,8 @@ public abstract class NetworkParameters {
protected int bip32HeaderP2PKHpriv;
protected int bip32HeaderP2WPKHpub;
protected int bip32HeaderP2WPKHpriv;
+ protected int bip32HeaderP2SHP2WPKHpub;
+ protected int bip32HeaderP2SHP2WPKHpriv;
/** Used to check majorities for block version upgrade */
protected int majorityEnforceBlockUpgrade;
@@ -347,6 +349,17 @@ public int getBip32HeaderP2WPKHpub() {
public int getBip32HeaderP2WPKHpriv() {
return bip32HeaderP2WPKHpriv;
}
+
+ /** Returns the 4 byte header for BIP32 wallet P2SH-P2WPKH - public key part. */
+ public int getBip32HeaderP2SHP2WPKHpub() {
+ return bip32HeaderP2SHP2WPKHpub;
+ }
+
+ /** Returns the 4 byte header for BIP32 wallet P2SH-P2WPKH - private key part. */
+ public int getBip32HeaderP2SHP2WPKHpriv() {
+ return bip32HeaderP2SHP2WPKHpriv;
+ }
+
/**
* 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
diff --git a/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java b/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java
index 2fd4e936d..c888cc667 100644
--- a/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java
+++ b/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java
@@ -147,7 +147,17 @@ public ECKey getConnectedKey(KeyBag keyBag) throws ScriptException {
} else if (ScriptPattern.isP2WPKH(connectedScript)) {
byte[] addressBytes = ScriptPattern.extractHashFromP2WH(connectedScript);
return keyBag.findKeyFromPubKeyHash(addressBytes, Script.ScriptType.P2WPKH);
- } else if (ScriptPattern.isP2PK(connectedScript)) {
+ } else if (ScriptPattern.isP2SH(connectedScript)) {
+ byte[] addressBytes = ScriptPattern.extractHashFromP2SH(connectedScript);
+ RedeemData redeemData = keyBag.findRedeemDataFromScriptHash(addressBytes);
+ if(redeemData != null) {
+ Script redeemScript = redeemData.redeemScript;
+ byte[] witnessHash = ScriptPattern.extractHashFromP2WH(redeemScript);
+ return keyBag.findKeyFromPubKeyHash(witnessHash, Script.ScriptType.P2SH_P2WPKH);
+ } else {
+ throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Could not understand form of connected output script: " + connectedScript);
+ }
+ }else if (ScriptPattern.isP2PK(connectedScript)) {
byte[] pubkeyBytes = ScriptPattern.extractKeyFromP2PK(connectedScript);
return keyBag.findKeyFromPubKey(pubkeyBytes);
} else {
diff --git a/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java b/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java
index 45fcee64f..e8f5fc3e9 100644
--- a/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java
+++ b/core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java
@@ -17,6 +17,7 @@
package org.bitcoinj.crypto;
import org.bitcoinj.core.*;
+import org.bitcoinj.script.Script;
import org.bouncycastle.crypto.generators.SCrypt;
import com.google.common.primitives.Bytes;
@@ -118,7 +119,7 @@ public String toBase58() {
public ECKey decrypt(String passphrase) throws BadPassphraseException {
String normalizedPassphrase = Normalizer.normalize(passphrase, Normalizer.Form.NFC);
ECKey key = ecMultiply ? decryptEC(normalizedPassphrase) : decryptNoEC(normalizedPassphrase);
- Sha256Hash hash = Sha256Hash.twiceOf(LegacyAddress.fromKey(params, key).toString().getBytes(StandardCharsets.US_ASCII));
+ Sha256Hash hash = Sha256Hash.twiceOf(LegacyAddress.fromKey(params, key, Script.ScriptType.P2PKH).toString().getBytes(StandardCharsets.US_ASCII));
byte[] actualAddressHash = Arrays.copyOfRange(hash.getBytes(), 0, 4);
if (!Arrays.equals(actualAddressHash, addressHash))
throw new BadPassphraseException();
diff --git a/core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java b/core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java
index eb6c04d10..9a6ae77f9 100644
--- a/core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java
+++ b/core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java
@@ -487,6 +487,8 @@ private byte[] serialize(NetworkParameters params, boolean pub, Script.ScriptTyp
ser.putInt(pub ? params.getBip32HeaderP2PKHpub() : params.getBip32HeaderP2PKHpriv());
else if (outputScriptType == Script.ScriptType.P2WPKH)
ser.putInt(pub ? params.getBip32HeaderP2WPKHpub() : params.getBip32HeaderP2WPKHpriv());
+ else if (outputScriptType == Script.ScriptType.P2SH_P2WPKH)
+ ser.putInt(pub ? params.getBip32HeaderP2SHP2WPKHpub() : params.getBip32HeaderP2SHP2WPKHpriv());
else
throw new IllegalStateException(outputScriptType.toString());
ser.put((byte) getDepth());
diff --git a/core/src/main/java/org/bitcoinj/params/MainNetParams.java b/core/src/main/java/org/bitcoinj/params/MainNetParams.java
index 6a591bb46..905749966 100644
--- a/core/src/main/java/org/bitcoinj/params/MainNetParams.java
+++ b/core/src/main/java/org/bitcoinj/params/MainNetParams.java
@@ -47,6 +47,8 @@ public MainNetParams() {
bip32HeaderP2PKHpriv = 0x019d9cfe; // The 4 byte header that serializes in base58 to "Ltpv"
bip32HeaderP2WPKHpub = 0x04b24746; // The 4 byte header that serializes in base58 to "zpub".
bip32HeaderP2WPKHpriv = 0x04b2430c; // The 4 byte header that serializes in base58 to "zprv"
+ bip32HeaderP2SHP2WPKHpub = 0x049d7cb2; // The 4 byte header that serializes in base58 to "ypub"
+ bip32HeaderP2SHP2WPKHpriv = 0x049d7878; // The 4 byte header that serializes in base58 to "yprv"
majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED;
diff --git a/core/src/main/java/org/bitcoinj/params/TestNet3Params.java b/core/src/main/java/org/bitcoinj/params/TestNet3Params.java
index 45b4740dd..1a0990904 100644
--- a/core/src/main/java/org/bitcoinj/params/TestNet3Params.java
+++ b/core/src/main/java/org/bitcoinj/params/TestNet3Params.java
@@ -73,6 +73,8 @@ public TestNet3Params() {
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"
+ bip32HeaderP2SHP2WPKHpub = 0x044a5262; // The 4 byte header that serializes in base58 to "upub".
+ bip32HeaderP2SHP2WPKHpriv = 0x044a4e28; // The 4 byte header that serializes in base58 to "uprv"
majorityEnforceBlockUpgrade = TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED;
diff --git a/core/src/main/java/org/bitcoinj/script/Script.java b/core/src/main/java/org/bitcoinj/script/Script.java
index 2b88f9d9f..8c3fd10c4 100644
--- a/core/src/main/java/org/bitcoinj/script/Script.java
+++ b/core/src/main/java/org/bitcoinj/script/Script.java
@@ -59,7 +59,8 @@ public enum ScriptType {
P2PK(2), // pay to pubkey
P2SH(3), // pay to script hash
P2WPKH(4), // pay to witness pubkey hash
- P2WSH(5); // pay to witness script hash
+ P2WSH(5), // pay to witness script hash
+ P2SH_P2WPKH(6);
public final int id;
@@ -280,7 +281,7 @@ public Address getToAddress(NetworkParameters params, boolean forcePayToPubKey)
else if (ScriptPattern.isP2SH(this))
return LegacyAddress.fromScriptHash(params, ScriptPattern.extractHashFromP2SH(this));
else if (forcePayToPubKey && ScriptPattern.isP2PK(this))
- return LegacyAddress.fromKey(params, ECKey.fromPublicOnly(ScriptPattern.extractKeyFromP2PK(this)));
+ return LegacyAddress.fromKey(params, ECKey.fromPublicOnly(ScriptPattern.extractKeyFromP2PK(this)), ScriptType.P2PKH);
else if (ScriptPattern.isP2WH(this))
return SegwitAddress.fromHash(params, ScriptPattern.extractHashFromP2WH(this));
else
diff --git a/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java b/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java
index 727de82b5..bff76b831 100644
--- a/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java
+++ b/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java
@@ -477,6 +477,32 @@ public static Script createP2WPKHOutputScript(ECKey key) {
return createP2WPKHOutputScript(key.getPubKeyHash());
}
+ /**
+ * Creates a segwit scriptPubKey that sends to the given public key hash.
+ */
+ public static Script createP2SHP2WPKHOutputScript(byte[] hash) {
+ checkArgument(hash.length == SegwitAddress.WITNESS_PROGRAM_LENGTH_PKH);
+ Script p2wpkhRedeemScript = createP2WPKHOutputScript(hash);
+ return ScriptBuilder.createP2SHOutputScript(p2wpkhRedeemScript);
+ }
+
+ /**
+ * Creates a segwit scriptPubKey that sends to the given public key.
+ */
+ public static Script createP2SHP2WPKHOutputScript(ECKey key) {
+ checkArgument(key.isCompressed());
+ Script p2wpkhRedeemScript = createP2WPKHOutputScript(key);
+ return ScriptBuilder.createP2SHOutputScript(p2wpkhRedeemScript);
+ }
+
+ /**
+ * Creates a segwit scriptPubKey that sends to the given public key.
+ */
+ public static Script createP2SHP2WPKHRedeemScript(ECKey key) {
+ checkArgument(key.isCompressed());
+ return createP2WPKHOutputScript(key);
+ }
+
/**
* Creates a scriptPubKey that sends to the given script hash. Read
* BIP 16 to learn more about this
diff --git a/core/src/main/java/org/bitcoinj/signers/LocalTransactionSigner.java b/core/src/main/java/org/bitcoinj/signers/LocalTransactionSigner.java
index 6916d1d4f..4598b9a47 100644
--- a/core/src/main/java/org/bitcoinj/signers/LocalTransactionSigner.java
+++ b/core/src/main/java/org/bitcoinj/signers/LocalTransactionSigner.java
@@ -116,7 +116,7 @@ public boolean signInputs(ProposedTransaction propTx, KeyBag keyBag) {
byte[] script = redeemData.redeemScript.getProgram();
try {
if (ScriptPattern.isP2PK(scriptPubKey) || ScriptPattern.isP2PKH(scriptPubKey)
- || ScriptPattern.isP2SH(scriptPubKey)) {
+ || ScriptPattern.isP2SH(scriptPubKey) && !ScriptPattern.isP2WPKH(redeemData.redeemScript)) {
TransactionSignature signature = tx.calculateSignature(i, key, script, Transaction.SigHash.ALL,
false);
@@ -140,6 +140,14 @@ public boolean signInputs(ProposedTransaction propTx, KeyBag keyBag) {
Transaction.SigHash.ALL, false);
txIn.setScriptSig(ScriptBuilder.createEmpty());
txIn.setWitness(TransactionWitness.redeemP2WPKH(signature, key));
+ } else if(ScriptPattern.isP2SH(scriptPubKey) && ScriptPattern.isP2WPKH(redeemData.redeemScript)) {
+ Script redeemScript = ScriptBuilder.createP2WPKHOutputScript(key);
+ Script witnessScript = ScriptBuilder.createP2PKHOutputScript(key);
+ Coin value = txIn.getValue();
+ TransactionSignature signature = tx.calculateWitnessSignature(i, key, witnessScript, value,
+ Transaction.SigHash.ALL, false);
+ txIn.setScriptSig(new ScriptBuilder().data(redeemScript.getProgram()).build());
+ txIn.setWitness(TransactionWitness.redeemP2WPKH(signature, key));
} else {
throw new IllegalStateException(script.toString());
}
diff --git a/core/src/main/java/org/bitcoinj/store/DatabaseFullPrunedBlockStore.java b/core/src/main/java/org/bitcoinj/store/DatabaseFullPrunedBlockStore.java
index a599105bb..c3b21e765 100644
--- a/core/src/main/java/org/bitcoinj/store/DatabaseFullPrunedBlockStore.java
+++ b/core/src/main/java/org/bitcoinj/store/DatabaseFullPrunedBlockStore.java
@@ -1133,7 +1133,7 @@ public List getOpenTransactionOutputs(List keys) throws UTXOProvide
s = conn.get().prepareStatement(getTransactionOutputSelectSQL());
for (ECKey key : keys) {
// TODO switch to pubKeyHash in order to support native segwit addresses
- s.setString(1, LegacyAddress.fromKey(params, key).toString());
+ s.setString(1, LegacyAddress.fromKey(params, key, ScriptType.P2PKH).toString());
ResultSet rs = s.executeQuery();
while (rs.next()) {
Sha256Hash hash = Sha256Hash.wrap(rs.getBytes(1));
diff --git a/core/src/main/java/org/bitcoinj/store/MemoryFullPrunedBlockStore.java b/core/src/main/java/org/bitcoinj/store/MemoryFullPrunedBlockStore.java
index 9990e68ea..db7a43815 100644
--- a/core/src/main/java/org/bitcoinj/store/MemoryFullPrunedBlockStore.java
+++ b/core/src/main/java/org/bitcoinj/store/MemoryFullPrunedBlockStore.java
@@ -18,6 +18,7 @@
import org.bitcoinj.core.*;
import com.google.common.base.Preconditions;
+import org.bitcoinj.script.Script;
import javax.annotation.Nullable;
import java.util.*;
@@ -416,7 +417,7 @@ public List getOpenTransactionOutputs(List keys) throws UTXOProvide
for (UTXO output : outputsList) {
for (ECKey key : keys) {
// TODO switch to pubKeyHash in order to support native segwit addresses
- Address address = LegacyAddress.fromKey(params, key);
+ Address address = LegacyAddress.fromKey(params, key, Script.ScriptType.P2PKH);
if (output.getAddress().equals(address.toString())) {
foundOutputs.add(output);
}
diff --git a/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java b/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java
index c423211f0..e9cefe064 100644
--- a/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java
+++ b/core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java
@@ -32,6 +32,8 @@ public DeterministicKeyChain makeKeyChain(DeterministicSeed seed, KeyCrypter cry
DeterministicKeyChain chain;
if (isMarried)
chain = new MarriedKeyChain(seed, crypter, outputScriptType, accountPath);
+ else if(outputScriptType == Script.ScriptType.P2SH_P2WPKH)
+ chain = new NestedSegwitKeyChain(seed, crypter, outputScriptType, accountPath);
else
chain = new DeterministicKeyChain(seed, crypter, outputScriptType, accountPath);
return chain;
@@ -45,6 +47,8 @@ public DeterministicKeyChain makeWatchingKeyChain(DeterministicKey accountKey, b
chain = new MarriedKeyChain(accountKey, outputScriptType);
else if (isFollowingKey)
chain = DeterministicKeyChain.builder().watchAndFollow(accountKey).outputScriptType(outputScriptType).build();
+ else if(outputScriptType == Script.ScriptType.P2SH_P2WPKH)
+ chain = NestedSegwitKeyChain.builder().watch(accountKey).outputScriptType(outputScriptType).build();
else
chain = DeterministicKeyChain.builder().watch(accountKey).outputScriptType(outputScriptType).build();
return chain;
@@ -56,6 +60,8 @@ public DeterministicKeyChain makeSpendingKeyChain(DeterministicKey accountKey, b
DeterministicKeyChain chain;
if (isMarried)
chain = new MarriedKeyChain(accountKey, outputScriptType);
+ else if (outputScriptType == Script.ScriptType.P2SH_P2WPKH)
+ chain = NestedSegwitKeyChain.builder().spend(accountKey).outputScriptType(outputScriptType).build();
else
chain = DeterministicKeyChain.builder().spend(accountKey).outputScriptType(outputScriptType).build();
return chain;
diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java
index 5dacc8c4e..459889199 100644
--- a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java
+++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java
@@ -117,6 +117,9 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
// m / 44' / 2' / 0'
public static final HDPath BIP44_ACCOUNT_ZERO_PATH = HDPath.M(new ChildNumber(44, true))
.extend(new ChildNumber(2, true), ChildNumber.ZERO_HARDENED);
+ // m / 49' / 2' / 0'
+ public static final HDPath BIP49_ACCOUNT_ZERO_PATH = HDPath.M(new ChildNumber(49, true))
+ .extend(new ChildNumber(2, true), ChildNumber.ZERO_HARDENED);
// m / 44' / 2' / 0'
public static final HDPath BIP84_ACCOUNT_ZERO_PATH = HDPath.M(new ChildNumber(84, true))
.extend(new ChildNumber(2, true), ChildNumber.ZERO_HARDENED);
@@ -363,7 +366,8 @@ public DeterministicKeyChain(DeterministicKey key, boolean isFollowing, boolean
protected DeterministicKeyChain(DeterministicSeed seed, @Nullable KeyCrypter crypter,
Script.ScriptType outputScriptType, List accountPath) {
checkArgument(outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH
- || outputScriptType == Script.ScriptType.P2WPKH, "Only P2PKH or P2WPKH allowed.");
+ || outputScriptType == Script.ScriptType.P2WPKH
+ || outputScriptType == Script.ScriptType.P2SH_P2WPKH, "Only P2PKH or P2WPKH allowed.");
this.outputScriptType = outputScriptType != null ? outputScriptType : Script.ScriptType.P2PKH;
this.accountPath = HDPath.M(accountPath);
this.seed = seed;
@@ -1361,6 +1365,10 @@ public boolean isMarried() {
return false;
}
+ public boolean isNestedSegwit() {
+ return false;
+ }
+
/** Get redeem data for a key. Only applicable to married keychains. */
public RedeemData getRedeemData(DeterministicKey followedKey) {
throw new UnsupportedOperationException();
diff --git a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java
index 3a76b2bf4..98c29c119 100644
--- a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java
+++ b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java
@@ -127,6 +127,16 @@ public Builder fromSeed(DeterministicSeed seed, Script.ScriptType outputScriptTy
this.chains.clear();
this.chains.add(fallbackChain);
this.chains.add(defaultChain);
+ } else if (outputScriptType == ScriptType.P2SH_P2WPKH) {
+ DeterministicKeyChain fallbackChain = DeterministicKeyChain.builder().seed(seed)
+ .outputScriptType(ScriptType.P2WPKH)
+ .accountPath(structure.accountPathFor(Script.ScriptType.P2SH_P2WPKH)).build();
+ NestedSegwitKeyChain defaultChain = NestedSegwitKeyChain.builder().seed(seed)
+ .outputScriptType(Script.ScriptType.P2SH_P2WPKH)
+ .accountPath(structure.accountPathFor(Script.ScriptType.P2SH_P2WPKH)).build();
+ this.chains.clear();
+ this.chains.add(fallbackChain);
+ this.chains.add(defaultChain);
} else {
throw new IllegalArgumentException(outputScriptType.toString());
}
@@ -160,6 +170,16 @@ public Builder fromKey(DeterministicKey accountKey, Script.ScriptType outputScri
this.chains.clear();
this.chains.add(fallbackChain);
this.chains.add(defaultChain);
+ } else if(outputScriptType == ScriptType.P2SH_P2WPKH) {
+ DeterministicKeyChain fallbackChain = DeterministicKeyChain.builder().spend(accountKey)
+ .outputScriptType(ScriptType.P2WPKH)
+ .accountPath(structure.accountPathFor(Script.ScriptType.P2PKH)).build();
+ NestedSegwitKeyChain defaultChain = NestedSegwitKeyChain.builder().spend(accountKey)
+ .outputScriptType(Script.ScriptType.P2SH_P2WPKH)
+ .accountPath(structure.accountPathFor(Script.ScriptType.P2SH_P2WPKH)).build();
+ this.chains.clear();
+ this.chains.add(fallbackChain);
+ this.chains.add(defaultChain);
} else {
throw new IllegalArgumentException(outputScriptType.toString());
}
@@ -279,6 +299,14 @@ else if (params.getId().equals(NetworkParameters.ID_UNITTESTNET))
.getToAddress(params);
currentAddresses.put(entry.getKey(), address);
}
+ } else if(isNestedSegwit()) {
+ maybeLookaheadScripts();
+ for (Map.Entry entry : this.currentKeys.entrySet()) {
+ Address address = ScriptBuilder
+ .createP2SHOutputScript(getActiveKeyChain().getRedeemData(entry.getValue()).redeemScript)
+ .getToAddress(params);
+ currentAddresses.put(entry.getKey(), address);
+ }
}
}
@@ -351,6 +379,13 @@ public Address currentAddress(KeyChain.KeyPurpose purpose) {
currentAddresses.put(purpose, current);
}
return current;
+ } else if(chain.isNestedSegwit()) {
+ Address current = currentAddresses.get(purpose);
+ if (current == null) {
+ current = freshAddress(purpose);
+ currentAddresses.put(purpose, current);
+ }
+ return current;
} else if (outputScriptType == Script.ScriptType.P2PKH || outputScriptType == Script.ScriptType.P2WPKH) {
return Address.fromKey(params, currentKey(purpose), outputScriptType);
} else {
@@ -421,6 +456,14 @@ public Address freshAddress(KeyChain.KeyPurpose purpose) {
maybeLookaheadScripts();
currentAddresses.put(purpose, freshAddress);
return freshAddress;
+ } else if(chain.isNestedSegwit()) {
+ Script outputScript = chain.freshOutputScript(purpose);
+ checkState(ScriptPattern.isP2SH(outputScript)); // Only handle P2SH for now
+ Address freshAddress = LegacyAddress.fromScriptHash(params,
+ ScriptPattern.extractHashFromP2SH(outputScript));
+ 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 {
@@ -695,6 +738,10 @@ public final boolean isMarried() {
return chains != null && !chains.isEmpty() && getActiveKeyChain().isMarried();
}
+ public final boolean isNestedSegwit() {
+ return chains != null && !chains.isEmpty() && getActiveKeyChain().isNestedSegwit();
+ }
+
/**
* Encrypt the keys in the group using the KeyCrypter and the AES key. A good default KeyCrypter to use is
* {@link KeyCrypterScrypt}.
@@ -992,7 +1039,7 @@ public void upgradeToDeterministic(Script.ScriptType preferredScriptType, KeyCha
log.info(
"Upgrading from basic keychain to P2PKH deterministic keychain. Using oldest non-rotating private key (address: {})",
- LegacyAddress.fromKey(params, keyToUse));
+ LegacyAddress.fromKey(params, keyToUse, preferredScriptType));
byte[] entropy = checkNotNull(keyToUse.getSecretBytes());
// Private keys should be at least 128 bits long.
checkState(entropy.length >= DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS / 8);
diff --git a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroupStructure.java b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroupStructure.java
index 55f909bd6..546bdf926 100644
--- a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroupStructure.java
+++ b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroupStructure.java
@@ -32,6 +32,8 @@ public HDPath accountPathFor(Script.ScriptType outputScriptType) {
return DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH;
else if (outputScriptType == Script.ScriptType.P2WPKH)
return DeterministicKeyChain.BIP84_ACCOUNT_ZERO_PATH;
+ else if(outputScriptType == Script.ScriptType.P2SH_P2WPKH)
+ return DeterministicKeyChain.BIP49_ACCOUNT_ZERO_PATH;
else
throw new IllegalArgumentException(outputScriptType.toString());
}
diff --git a/core/src/main/java/org/bitcoinj/wallet/NestedSegwitKeyChain.java b/core/src/main/java/org/bitcoinj/wallet/NestedSegwitKeyChain.java
new file mode 100644
index 000000000..3bd040845
--- /dev/null
+++ b/core/src/main/java/org/bitcoinj/wallet/NestedSegwitKeyChain.java
@@ -0,0 +1,337 @@
+/*
+ * 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 com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.protobuf.ByteString;
+
+import org.bitcoinj.core.BloomFilter;
+import org.bitcoinj.core.ECKey;
+import org.bitcoinj.core.NetworkParameters;
+import org.bitcoinj.core.Utils;
+import org.bitcoinj.crypto.*;
+import org.bitcoinj.script.Script;
+import org.bitcoinj.script.ScriptBuilder;
+import org.bitcoinj.utils.Threading;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.encoders.Hex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * A multi-signature keychain using synchronized HD keys (a.k.a HDM)
+ * This keychain keeps track of following keychains that follow the account key of this keychain.
+ * You can get P2SH addresses to receive coins to from this chain. The threshold - sigsRequiredToSpend
+ * specifies how many signatures required to spend transactions for this married keychain. This value should not exceed
+ * total number of keys involved (one followed key plus number of following keys), otherwise IllegalArgumentException
+ * will be thrown.
+ * IMPORTANT: As of Bitcoin Core 0.9 all bare (non-P2SH) multisig transactions which require more than 3 public keys are non-standard
+ * and such spends won't be processed by peers with default settings, essentially making such transactions almost
+ * nonspendable
+ * This method will throw an IllegalStateException, if the keychain is already married or already has leaf keys
+ * issued.
+ */
+public class NestedSegwitKeyChain extends DeterministicKeyChain {
+ protected final ReentrantLock lock = Threading.lock(DeterministicKeyChain.class);
+
+ // The map holds P2SH redeem script and corresponding ECKeys issued by this KeyChainGroup (including lookahead)
+ // mapped to redeem script hashes.
+ private LinkedHashMap p2shP2wpkhRedeemData = new LinkedHashMap<>();
+
+ public static class Builder> extends DeterministicKeyChain.Builder {
+ protected SecureRandom random;
+ protected int bits = DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS;
+ protected String passphrase;
+ 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;
+ protected HDPath accountPath = null;
+
+ protected Builder() {
+ }
+
+ @SuppressWarnings("unchecked")
+ protected T self() {
+ return (T)this;
+ }
+
+ /**
+ * Creates a deterministic key chain starting from the given entropy. All keys yielded by this chain will be the same
+ * if the starting entropy is the same. You should provide the creation time in seconds since the UNIX epoch for the
+ * seed: this lets us know from what part of the chain we can expect to see derived keys appear.
+ */
+ public T entropy(byte[] entropy, long creationTimeSecs) {
+ this.entropy = entropy;
+ this.creationTimeSecs = creationTimeSecs;
+ return self();
+ }
+
+ /**
+ * Creates a deterministic key chain starting from the given seed. All keys yielded by this chain will be the same
+ * if the starting seed is the same.
+ */
+ public T seed(DeterministicSeed seed) {
+ this.seed = seed;
+ return self();
+ }
+
+ /**
+ * Generates a new key chain with entropy selected randomly from the given {@link SecureRandom}
+ * object and of the requested size in bits. The derived seed is further protected with a user selected passphrase
+ * (see BIP 39).
+ * @param random the random number generator - use new SecureRandom().
+ * @param bits The number of bits of entropy to use when generating entropy. Either 128 (default), 192 or 256.
+ */
+ public T random(SecureRandom random, int bits) {
+ this.random = random;
+ this.bits = bits;
+ return self();
+ }
+
+ /**
+ * Generates a new key chain with 128 bits of entropy selected randomly from the given {@link SecureRandom}
+ * object. The derived seed is further protected with a user selected passphrase
+ * (see BIP 39).
+ * @param random the random number generator - use new SecureRandom().
+ */
+ public T random(SecureRandom random) {
+ this.random = random;
+ return self();
+ }
+
+ /**
+ * Creates a key chain that watches the given account key.
+ */
+ public T watch(DeterministicKey accountKey) {
+ checkState(accountPath == null, "either watch or accountPath");
+ this.watchingKey = accountKey;
+ this.isFollowing = false;
+ return self();
+ }
+
+ /**
+ * Creates a deterministic key chain with the given watch key and that follows some other keychain. In a married
+ * wallet following keychain represents "spouse". Watch key has to be an account key.
+ */
+ public T watchAndFollow(DeterministicKey accountKey) {
+ checkState(accountPath == null, "either watchAndFollow or accountPath");
+ this.watchingKey = accountKey;
+ this.isFollowing = true;
+ return self();
+ }
+
+ /**
+ * Creates a key chain that can spend from the given account key.
+ */
+ public T spend(DeterministicKey accountKey) {
+ checkState(accountPath == null, "either spend or accountPath");
+ this.spendingKey = accountKey;
+ this.isFollowing = false;
+ 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
+ this.passphrase = passphrase;
+ return self();
+ }
+
+ /**
+ * Use an account path other than the default {@link DeterministicKeyChain#BIP44_ACCOUNT_ZERO_PATH}.
+ */
+ public T accountPath(List accountPath) {
+ checkState(watchingKey == null, "either watch or accountPath");
+ this.accountPath = HDPath.M(checkNotNull(accountPath));
+ return self();
+ }
+
+ public NestedSegwitKeyChain build() {
+ checkState(passphrase == null || seed == null, "Passphrase must not be specified with seed");
+
+ if (accountPath == null)
+ accountPath = BIP44_ACCOUNT_ZERO_PATH;
+
+ if (random != null)
+ // Default passphrase to "" if not specified
+ return new NestedSegwitKeyChain(new DeterministicSeed(random, bits, getPassphrase()), null,
+ outputScriptType, accountPath);
+ else if (entropy != null)
+ return new NestedSegwitKeyChain(new DeterministicSeed(entropy, getPassphrase(), creationTimeSecs),
+ null, outputScriptType, accountPath);
+ else if (seed != null)
+ return new NestedSegwitKeyChain(seed, null, outputScriptType, accountPath);
+ else if (watchingKey != null)
+ return new NestedSegwitKeyChain(watchingKey, isFollowing, true, outputScriptType);
+ else if (spendingKey != null)
+ return new NestedSegwitKeyChain(spendingKey, false, false, outputScriptType);
+ else
+ throw new IllegalStateException();
+ }
+
+ protected String getPassphrase() {
+ return passphrase != null ? passphrase : DEFAULT_PASSPHRASE_FOR_MNEMONIC;
+ }
+ }
+
+ public static Builder> builder() {
+ return new Builder();
+ }
+
+ /**
+ * This constructor is not stable across releases! If you need a stable API, use {@link #builder()} to use a
+ * {@link Builder}.
+ */
+ protected NestedSegwitKeyChain(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 NestedSegwitKeyChain(DeterministicSeed seed, KeyCrypter crypter, Script.ScriptType outputScriptType, List accountPath) {
+ super(seed, crypter, outputScriptType, accountPath);
+ }
+
+ public NestedSegwitKeyChain(DeterministicKey key, boolean isFollowing, boolean isWatching,
+ Script.ScriptType outputScriptType) {
+ super(key, isFollowing, isWatching, outputScriptType);
+ }
+
+ @Override
+ public boolean isMarried() {
+ return false;
+ }
+
+ @Override
+ public boolean isNestedSegwit() {
+ return true;
+ }
+
+ /** Create a new married key and return the matching output script */
+ @Override
+ public Script freshOutputScript(KeyPurpose purpose) {
+ DeterministicKey myKey = getKey(purpose);
+ return ScriptBuilder.createP2SHP2WPKHOutputScript(myKey);
+ }
+
+ /** Get the redeem data for a key in this married chain */
+ @Override
+ public RedeemData getRedeemData(DeterministicKey myKey) {
+ Script redeemScript = ScriptBuilder.createP2SHP2WPKHRedeemScript(myKey);
+ return RedeemData.of(myKey, redeemScript);
+ }
+
+ @Override
+ public List serializeToProtobuf() {
+ List result = new ArrayList<>();
+ lock.lock();
+ try {
+ result.addAll(serializeMyselfToProtobuf());
+ } finally {
+ lock.unlock();
+ }
+ return result;
+ }
+
+ @Override
+ protected void formatAddresses(boolean includeLookahead, boolean includePrivateKeys, @Nullable KeyParameter aesKey,
+ NetworkParameters params, StringBuilder builder) {
+ builder.append('\n');
+ for (RedeemData redeemData : p2shP2wpkhRedeemData.values())
+ formatScript(ScriptBuilder.createP2SHOutputScript(redeemData.redeemScript), builder, params);
+ }
+
+ private void formatScript(Script script, StringBuilder builder, NetworkParameters params) {
+ builder.append(" addr:");
+ builder.append(script.getToAddress(params));
+ builder.append(" hash160:");
+ builder.append(Utils.HEX.encode(script.getPubKeyHash()));
+ if (script.getCreationTimeSeconds() > 0)
+ builder.append(" creationTimeSeconds:").append(script.getCreationTimeSeconds());
+ builder.append('\n');
+ }
+
+ @Override
+ public void maybeLookAheadScripts() {
+ super.maybeLookAheadScripts();
+ int numLeafKeys = getLeafKeys().size();
+
+ checkState(p2shP2wpkhRedeemData.size() <= numLeafKeys, "Number of scripts is greater than number of leaf keys");
+ if (p2shP2wpkhRedeemData.size() == numLeafKeys)
+ return;
+
+ maybeLookAhead();
+ for (DeterministicKey followedKey : getLeafKeys()) {
+ RedeemData redeemData = getRedeemData(followedKey);
+ Script scriptPubKey = ScriptBuilder.createP2SHOutputScript(redeemData.redeemScript);
+ p2shP2wpkhRedeemData.put(ByteString.copyFrom(scriptPubKey.getPubKeyHash()), redeemData);
+ }
+ }
+
+ @Nullable
+ @Override
+ public RedeemData findRedeemDataByScriptHash(ByteString bytes) {
+ return p2shP2wpkhRedeemData.get(bytes);
+ }
+
+ @Override
+ public BloomFilter getFilter(int size, double falsePositiveRate, long tweak) {
+ lock.lock();
+ BloomFilter filter;
+ try {
+ filter = new BloomFilter(size, falsePositiveRate, tweak);
+ for (Map.Entry entry : p2shP2wpkhRedeemData.entrySet()) {
+ filter.insert(entry.getKey().toByteArray());
+ filter.insert(entry.getValue().redeemScript.getProgram());
+ }
+ } finally {
+ lock.unlock();
+ }
+ return filter;
+ }
+
+ @Override
+ public int numBloomFilterEntries() {
+ maybeLookAhead();
+ return getLeafKeys().size() * 2;
+ }
+}
diff --git a/core/src/main/java/org/bitcoinj/wallet/Protos.java b/core/src/main/java/org/bitcoinj/wallet/Protos.java
index 743fd7b99..d700a57fc 100644
--- a/core/src/main/java/org/bitcoinj/wallet/Protos.java
+++ b/core/src/main/java/org/bitcoinj/wallet/Protos.java
@@ -3484,6 +3484,10 @@ public enum OutputScriptType
* P2WPKH = 2;
*/
P2WPKH(2),
+ /**
+ * P2SH_P2WPKH = 3;
+ */
+ P2SH_P2WPKH(3),
;
/**
@@ -3494,6 +3498,10 @@ public enum OutputScriptType
* P2WPKH = 2;
*/
public static final int P2WPKH_VALUE = 2;
+ /**
+ * P2SH_P2WPKH = 3;
+ */
+ public static final int P2SH_P2WPKH_VALUE = 3;
public final int getNumber() {
@@ -3518,6 +3526,7 @@ public static OutputScriptType forNumber(int value) {
switch (value) {
case 1: return P2PKH;
case 2: return P2WPKH;
+ case 3: return P2SH_P2WPKH;
default: return null;
}
}
@@ -22364,7 +22373,7 @@ public org.bitcoinj.wallet.Protos.ExchangeRate getDefaultInstanceForType() {
"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\"\231\004\n\003Key\022\036\n\004type\030\001 \002(\0162\020.wallet.Key." +
+ "\r:\0011\"\252\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" +
@@ -22376,68 +22385,69 @@ public org.bitcoinj.wallet.Protos.ExchangeRate getDefaultInstanceForType() {
"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"
+ "\n\021DETERMINISTIC_KEY\020\004\":\n\020OutputScriptTyp" +
+ "e\022\t\n\005P2PKH\020\001\022\n\n\006P2WPKH\020\002\022\017\n\013P2SH_P2WPKH\020" +
+ "\003\"5\n\006Script\022\017\n\007program\030\001 \002(\014\022\032\n\022creation" +
+ "_timestamp\030\002 \002(\003\"\035\n\rScriptWitness\022\014\n\004dat" +
+ "a\030\001 \003(\014\"\272\001\n\020TransactionInput\022\"\n\032transact" +
+ "ion_out_point_hash\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\007" +
+ "witness\030\006 \001(\0132\025.wallet.ScriptWitness\"\177\n\021" +
+ "TransactionOutput\022\r\n\005value\030\001 \002(\003\022\024\n\014scri" +
+ "pt_bytes\030\002 \002(\014\022!\n\031spent_by_transaction_h" +
+ "ash\030\003 \001(\014\022\"\n\032spent_by_transaction_index\030" +
+ "\004 \001(\005\"\267\003\n\025TransactionConfidence\0220\n\004type\030" +
+ "\001 \001(\0162\".wallet.TransactionConfidence.Typ" +
+ "e\022\032\n\022appeared_at_height\030\002 \001(\005\022\036\n\026overrid" +
+ "ing_transaction\030\003 \001(\014\022\r\n\005depth\030\004 \001(\005\022)\n\014" +
+ "broadcast_by\030\006 \003(\0132\023.wallet.PeerAddress\022" +
+ "\033\n\023last_broadcasted_at\030\010 \001(\003\0224\n\006source\030\007" +
+ " \001(\0162$.wallet.TransactionConfidence.Sour" +
+ "ce\"`\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\004DEA" +
+ "D\020\004\022\017\n\013IN_CONFLICT\020\005\"A\n\006Source\022\022\n\016SOURCE" +
+ "_UNKNOWN\020\000\022\022\n\016SOURCE_NETWORK\020\001\022\017\n\013SOURCE" +
+ "_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.Tr" +
+ "ansaction.Pool\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nupd" +
+ "ated_at\030\005 \001(\003\0223\n\021transaction_input\030\006 \003(\013" +
+ "2\030.wallet.TransactionInput\0225\n\022transactio" +
+ "n_output\030\007 \003(\0132\031.wallet.TransactionOutpu" +
+ "t\022\022\n\nblock_hash\030\010 \003(\014\022 \n\030block_relativit" +
+ "y_offsets\030\013 \003(\005\0221\n\nconfidence\030\t \001(\0132\035.wa" +
+ "llet.TransactionConfidence\0225\n\007purpose\030\n " +
+ "\001(\0162\033.wallet.Transaction.Purpose:\007UNKNOW" +
+ "N\022+\n\rexchange_rate\030\014 \001(\0132\024.wallet.Exchan" +
+ "geRate\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\010INACTIVE\020\002\022\010\n\004DEAD\020\n\022\013\n\007" +
+ "PENDING\020\020\022\024\n\020PENDING_INACTIVE\020\022\"\243\001\n\007Purp" +
+ "ose\022\013\n\007UNKNOWN\020\000\022\020\n\014USER_PAYMENT\020\001\022\020\n\014KE" +
+ "Y_ROTATION\020\002\022\034\n\030ASSURANCE_CONTRACT_CLAIM" +
+ "\020\003\022\035\n\031ASSURANCE_CONTRACT_PLEDGE\020\004\022\033\n\027ASS" +
+ "URANCE_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\t" +
+ "mandatory\030\003 \002(\010\" \n\003Tag\022\013\n\003tag\030\001 \002(\t\022\014\n\004d" +
+ "ata\030\002 \002(\014\"\261\004\n\006Wallet\022\032\n\022network_identifi" +
+ "er\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_s" +
+ "een_block_time_secs\030\016 \001(\003\022\030\n\003key\030\003 \003(\0132\013" +
+ ".wallet.Key\022(\n\013transaction\030\004 \003(\0132\023.walle" +
+ "t.Transaction\022&\n\016watched_script\030\017 \003(\0132\016." +
+ "wallet.Script\022C\n\017encryption_type\030\005 \001(\0162\035" +
+ ".wallet.Wallet.EncryptionType:\013UNENCRYPT" +
+ "ED\0227\n\025encryption_parameters\030\006 \001(\0132\030.wall" +
+ "et.ScryptParameters\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\016Encr" +
+ "yptionType\022\017\n\013UNENCRYPTED\020\001\022\030\n\024ENCRYPTED" +
+ "_SCRYPT_AES\020\002\"R\n\014ExchangeRate\022\022\n\ncoin_va" +
+ "lue\030\001 \002(\003\022\022\n\nfiat_value\030\002 \002(\003\022\032\n\022fiat_cu" +
+ "rrency_code\030\003 \002(\tB\035\n\023org.bitcoinj.wallet" +
+ "B\006Protos"
};
descriptor = com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(descriptorData,
diff --git a/core/src/main/java/org/bitcoinj/wallet/RedeemData.java b/core/src/main/java/org/bitcoinj/wallet/RedeemData.java
index ff0426949..93113bff3 100644
--- a/core/src/main/java/org/bitcoinj/wallet/RedeemData.java
+++ b/core/src/main/java/org/bitcoinj/wallet/RedeemData.java
@@ -57,8 +57,7 @@ public static RedeemData of(List keys, Script redeemScript) {
* to spend such inputs.
*/
public static RedeemData of(ECKey key, Script redeemScript) {
- checkArgument(ScriptPattern.isP2PKH(redeemScript)
- || ScriptPattern.isP2WPKH(redeemScript) || ScriptPattern.isP2PK(redeemScript));
+ checkArgument(ScriptPattern.isP2SH(redeemScript) || ScriptPattern.isP2WPKH(redeemScript));
return key != null ? new RedeemData(Collections.singletonList(key), redeemScript) : null;
}
diff --git a/core/src/main/java/org/bitcoinj/wallet/Wallet.java b/core/src/main/java/org/bitcoinj/wallet/Wallet.java
index 4562a1a4d..f0f3e854c 100644
--- a/core/src/main/java/org/bitcoinj/wallet/Wallet.java
+++ b/core/src/main/java/org/bitcoinj/wallet/Wallet.java
@@ -69,6 +69,7 @@
import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener;
import org.bitcoinj.wallet.listeners.WalletCoinsSentEventListener;
import org.bitcoinj.wallet.listeners.WalletReorganizeEventListener;
+import org.bouncycastle.util.encoders.Hex;
import org.slf4j.*;
import org.bouncycastle.crypto.params.*;
@@ -317,7 +318,7 @@ public static Wallet fromSeed(NetworkParameters params, DeterministicSeed seed,
* @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}
+ * {@link DeterministicKeyChain#BIP44_ACCOUNT_ZERO_PATH 0 hardened path}
* @deprecated Use {@link #fromSeed(NetworkParameters, DeterministicSeed, ScriptType, KeyChainGroupStructure)}
*/
@Deprecated
@@ -448,6 +449,8 @@ private static Script.ScriptType outputScriptTypeFromB58(NetworkParameters param
return Script.ScriptType.P2PKH;
else if (header == params.getBip32HeaderP2WPKHpub() || header == params.getBip32HeaderP2WPKHpriv())
return Script.ScriptType.P2WPKH;
+ else if (header == params.getBip32HeaderP2SHP2WPKHpub() || header == params.getBip32HeaderP2SHP2WPKHpriv())
+ return Script.ScriptType.P2SH_P2WPKH;
else
throw new IllegalArgumentException(base58.substring(0, 4));
}
@@ -1195,7 +1198,7 @@ public boolean isWatchedScript(Script script) {
*/
public ECKey findKeyFromAddress(Address address) {
final ScriptType scriptType = address.getOutputScriptType();
- if (scriptType == ScriptType.P2PKH || scriptType == ScriptType.P2WPKH)
+ if (scriptType == ScriptType.P2PKH || scriptType == ScriptType.P2WPKH || scriptType == ScriptType.P2SH_P2WPKH)
return findKeyFromPubKeyHash(address.getHash(), scriptType);
else
return null;
@@ -4365,7 +4368,11 @@ public void signTransaction(SendRequest req) throws BadWalletEncryptionKeyExcept
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));
+ if(ScriptPattern.isP2SH(scriptPubKey) && ScriptPattern.isP2WPKH(redeemData.redeemScript)) {
+ txIn.setScriptSig(ScriptBuilder.createEmpty());
+ } else {
+ txIn.setScriptSig(scriptPubKey.createEmptyInputScript(redeemData.keys.get(0), redeemData.redeemScript));
+ }
txIn.setWitness(scriptPubKey.createEmptyWitness(redeemData.keys.get(0)));
}
@@ -4422,7 +4429,8 @@ public List calculateAllSpendCandidates(boolean excludeImmatu
if (vUTXOProvider == null) {
candidates = new ArrayList<>(myUnspents.size());
for (TransactionOutput output : myUnspents) {
- if (excludeUnsignable && !canSignFor(output.getScriptPubKey())) continue;
+ boolean canSignFor = canSignFor(output.getScriptPubKey());
+ if (excludeUnsignable && !canSignFor) continue;
Transaction transaction = checkNotNull(output.getParentTransaction());
if (excludeImmatureCoinbases && !transaction.isMature())
continue;
@@ -5187,11 +5195,17 @@ private int estimateVirtualBytesForSigning(CoinSelection selection) {
} else if (ScriptPattern.isP2WPKH(script)) {
key = findKeyFromPubKeyHash(ScriptPattern.extractHashFromP2WH(script), Script.ScriptType.P2WPKH);
checkNotNull(key, "Coin selection includes unspendable outputs");
- vsize += (script.getNumberOfBytesRequiredToSpend(key, redeemScript) + 3) / 4; // round up
+ vsize += 2 + (script.getNumberOfBytesRequiredToSpend(key, redeemScript) + 3) / 4; // round up
+ //added a two because sometimes it might be 1 below required, so now it is hopefully 1 above in edge cases
} else if (ScriptPattern.isP2SH(script)) {
redeemScript = findRedeemDataFromScriptHash(ScriptPattern.extractHashFromP2SH(script)).redeemScript;
- checkNotNull(redeemScript, "Coin selection includes unspendable outputs");
- vsize += script.getNumberOfBytesRequiredToSpend(key, redeemScript);
+ if(ScriptPattern.isP2WPKH(redeemScript)) {
+ checkNotNull(redeemScript, "Coin selection includes unspendable outputs");
+ vsize += redeemScript.getNumberOfBytesRequiredToSpend(key, redeemScript);
+ } else {
+ checkNotNull(redeemScript, "Coin selection includes unspendable outputs");
+ vsize += script.getNumberOfBytesRequiredToSpend(key, redeemScript);
+ }
} else {
vsize += script.getNumberOfBytesRequiredToSpend(key, redeemScript);
}
diff --git a/core/src/main/proto/wallet.proto b/core/src/main/proto/wallet.proto
index 628c8bf85..b2bab54d0 100644
--- a/core/src/main/proto/wallet.proto
+++ b/core/src/main/proto/wallet.proto
@@ -141,6 +141,7 @@ message Key {
enum OutputScriptType {
P2PKH = 1;
P2WPKH = 2;
+ P2SH_P2WPKH = 3;
}
// Type of addresses (aka output scripts) to generate for receiving.
optional OutputScriptType output_script_type = 11;
diff --git a/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java b/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java
index 6917c875c..96c531846 100644
--- a/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java
+++ b/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java
@@ -67,11 +67,11 @@ public void setup() {
// serialized data properly.
long secs = 1389353062L;
chain = DeterministicKeyChain.builder().entropy(ENTROPY, secs)
- .accountPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH).outputScriptType(Script.ScriptType.P2PKH).build();
+ .accountPath(DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH).outputScriptType(Script.ScriptType.P2PKH).build();
chain.setLookaheadSize(10);
segwitChain = DeterministicKeyChain.builder().entropy(ENTROPY, secs)
- .accountPath(DeterministicKeyChain.ACCOUNT_ONE_PATH).outputScriptType(Script.ScriptType.P2WPKH).build();
+ .accountPath(DeterministicKeyChain.BIP84_ACCOUNT_ZERO_PATH).outputScriptType(Script.ScriptType.P2WPKH).build();
segwitChain.setLookaheadSize(10);
bip44chain = DeterministicKeyChain.builder().entropy(ENTROPY, secs).accountPath(BIP44_COIN_1_ACCOUNT_ZERO_PATH)
@@ -242,7 +242,7 @@ public void serializeUnencrypted() throws UnreadableWalletException {
// Round trip the data back and forth to check it is preserved.
int oldLookaheadSize = chain.getLookaheadSize();
chain = DeterministicKeyChain.fromProtobuf(keys, null).get(0);
- assertEquals(DeterministicKeyChain.ACCOUNT_ZERO_PATH, chain.getAccountPath());
+ assertEquals(DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH, chain.getAccountPath());
assertEquals(EXPECTED_SERIALIZATION, protoToString(chain.serializeToProtobuf()));
assertEquals(key1, chain.findKeyFromPubHash(key1.getPubKeyHash()));
assertEquals(key2, chain.findKeyFromPubHash(key2.getPubKeyHash()));
@@ -424,7 +424,7 @@ public void watchingChain() throws UnreadableWalletException {
List serialization = chain.serializeToProtobuf();
checkSerialization(serialization, "watching-wallet-serialization.txt");
chain = DeterministicKeyChain.fromProtobuf(serialization, null).get(0);
- assertEquals(DeterministicKeyChain.ACCOUNT_ZERO_PATH, chain.getAccountPath());
+ assertEquals(DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH, chain.getAccountPath());
final DeterministicKey rekey4 = chain.getKey(KeyChain.KeyPurpose.CHANGE);
assertEquals(key4.getPubKeyPoint(), rekey4.getPubKeyPoint());
}
@@ -543,7 +543,7 @@ public void watchingSegwitChain() throws UnreadableWalletException {
List 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(DeterministicKeyChain.BIP84_ACCOUNT_ZERO_PATH, chain.getAccountPath());
assertEquals(Script.ScriptType.P2WPKH, chain.getOutputScriptType());
final DeterministicKey rekey4 = segwitChain.getKey(KeyChain.KeyPurpose.CHANGE);
assertEquals(key4.getPubKeyPoint(), rekey4.getPubKeyPoint());
@@ -583,7 +583,7 @@ public void spendingChain() throws UnreadableWalletException {
List serialization = chain.serializeToProtobuf();
checkSerialization(serialization, "spending-wallet-serialization.txt");
chain = DeterministicKeyChain.fromProtobuf(serialization, null).get(0);
- assertEquals(DeterministicKeyChain.ACCOUNT_ZERO_PATH, chain.getAccountPath());
+ assertEquals(DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH, chain.getAccountPath());
final DeterministicKey rekey4 = chain.getKey(KeyChain.KeyPurpose.CHANGE);
assertEquals(key4.getPubKeyPoint(), rekey4.getPubKeyPoint());
}
@@ -701,10 +701,10 @@ private void verifySpendableKeyChain(DeterministicKey firstReceiveKey, Determini
@Test(expected = IllegalStateException.class)
public void watchingCannotEncrypt() throws Exception {
- final DeterministicKey accountKey = chain.getKeyByPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH);
+ final DeterministicKey accountKey = chain.getKeyByPath(DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH);
chain = DeterministicKeyChain.builder().watch(accountKey.dropPrivateBytes().dropParent())
.outputScriptType(chain.getOutputScriptType()).build();
- assertEquals(DeterministicKeyChain.ACCOUNT_ZERO_PATH, chain.getAccountPath());
+ assertEquals(DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH, chain.getAccountPath());
chain = chain.toEncrypted("this doesn't make any sense");
}
diff --git a/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java b/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java
index 07af74b5e..0c33a373f 100644
--- a/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java
+++ b/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java
@@ -518,12 +518,12 @@ public void constructFromSeed() throws Exception {
public void addAndActivateHDChain_freshCurrentAddress() {
DeterministicSeed seed = new DeterministicSeed(ENTROPY, "", 0);
DeterministicKeyChain chain1 = DeterministicKeyChain.builder().seed(seed)
- .accountPath(DeterministicKeyChain.ACCOUNT_ZERO_PATH).outputScriptType(Script.ScriptType.P2PKH).build();
+ .accountPath(DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH).outputScriptType(Script.ScriptType.P2PKH).build();
group = KeyChainGroup.builder(MAINNET).addChain(chain1).build();
assertEquals("1M5T5k9yKtGWRtWYMjQtGx3K2sshrABzCT", group.currentAddress(KeyPurpose.RECEIVE_FUNDS).toString());
final DeterministicKeyChain chain2 = DeterministicKeyChain.builder().seed(seed)
- .accountPath(DeterministicKeyChain.ACCOUNT_ONE_PATH).outputScriptType(Script.ScriptType.P2PKH).build();
+ .accountPath(DeterministicKeyChain.BIP84_ACCOUNT_ZERO_PATH).outputScriptType(Script.ScriptType.P2PKH).build();
group.addAndActivateHDChain(chain2);
assertEquals("1JLnjJEXcyByAaW6sqSxNvGiiSEWRhdvPb", group.currentAddress(KeyPurpose.RECEIVE_FUNDS).toString());
@@ -676,7 +676,7 @@ public void isWatchingMixedKeys() {
public void segwitKeyChainGroup() throws Exception {
group = KeyChainGroup.builder(MAINNET).lookaheadSize(LOOKAHEAD_SIZE)
.addChain(DeterministicKeyChain.builder().entropy(ENTROPY, 0).outputScriptType(Script.ScriptType.P2WPKH)
- .accountPath(DeterministicKeyChain.ACCOUNT_ONE_PATH).build())
+ .accountPath(DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH).build())
.build();
assertEquals(Script.ScriptType.P2WPKH, group.getActiveKeyChain().getOutputScriptType());
assertEquals("bc1qhcurdec849thpjjp3e27atvya43gy2snrechd9",
diff --git a/core/src/test/java/org/bitcoinj/wallet/WalletTest.java b/core/src/test/java/org/bitcoinj/wallet/WalletTest.java
index bb1ce880e..2cd198a40 100644
--- a/core/src/test/java/org/bitcoinj/wallet/WalletTest.java
+++ b/core/src/test/java/org/bitcoinj/wallet/WalletTest.java
@@ -3482,7 +3482,7 @@ public boolean isReady() {
public boolean signInputs(ProposedTransaction propTx, KeyBag keyBag) {
assertEquals(propTx.partialTx.getInputs().size(), propTx.keyPaths.size());
List externalZeroLeaf = ImmutableList.builder()
- .addAll(DeterministicKeyChain.ACCOUNT_ZERO_PATH)
+ .addAll(DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH)
.addAll(DeterministicKeyChain.EXTERNAL_SUBPATH).add(ChildNumber.ZERO).build();
for (TransactionInput input : propTx.partialTx.getInputs()) {
List keypath = propTx.keyPaths.get(input.getConnectedOutput().getScriptPubKey());
diff --git a/tools/src/main/java/org/bitcoinj/tools/WalletTool.java b/tools/src/main/java/org/bitcoinj/tools/WalletTool.java
index 9fd8369f0..f312ff851 100644
--- a/tools/src/main/java/org/bitcoinj/tools/WalletTool.java
+++ b/tools/src/main/java/org/bitcoinj/tools/WalletTool.java
@@ -1093,7 +1093,7 @@ private void addKey() {
if (!key.isCompressed())
System.out.println("WARNING: Importing an uncompressed key");
wallet.importKey(key);
- System.out.print("Addresses: " + LegacyAddress.fromKey(params, key));
+ System.out.print("Addresses: " + LegacyAddress.fromKey(params, key, ScriptType.P2PKH));
if (key.isCompressed())
System.out.print("," + SegwitAddress.fromKey(params, key));
System.out.println();
diff --git a/wallettemplate/src/main/java/wallettemplate/Main.java b/wallettemplate/src/main/java/wallettemplate/Main.java
index 7469d4338..5b9ace751 100644
--- a/wallettemplate/src/main/java/wallettemplate/Main.java
+++ b/wallettemplate/src/main/java/wallettemplate/Main.java
@@ -52,7 +52,7 @@
public class Main extends Application {
public static NetworkParameters params = MainNetParams.get();
- public static final Script.ScriptType PREFERRED_OUTPUT_SCRIPT_TYPE = Script.ScriptType.P2PKH;
+ public static final Script.ScriptType PREFERRED_OUTPUT_SCRIPT_TYPE = Script.ScriptType.P2SH_P2WPKH;
public static final String APP_NAME = "WalletTemplate";
private static final String WALLET_FILE_NAME = APP_NAME.replaceAll("[^a-zA-Z0-9.-]", "_") + "-"
+ params.getPaymentProtocolId() + "-" + PREFERRED_OUTPUT_SCRIPT_TYPE.name();
diff --git a/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java b/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java
index b6544ede5..acf88d4dd 100644
--- a/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java
+++ b/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java
@@ -108,6 +108,7 @@ public void onFailure(Throwable t) {
} catch (InsufficientMoneyException e) {
informationalAlert("Could not empty the wallet",
"You may have too little money left in the wallet to make a transaction.");
+ e.printStackTrace();
overlayUI.done();
} catch (ECKey.KeyIsEncryptedException e) {
askForPasswordAndRetry();