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