Skip to content

Commit 35858f2

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents 611ac18 + ee157a3 commit 35858f2

29 files changed

+602
-101
lines changed

core/src/main/java/org/bitcoinj/core/Address.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ public static Address fromString(@Nullable NetworkParameters params, String str)
8080
* @return constructed address
8181
*/
8282
public static Address fromKey(final NetworkParameters params, final ECKey key, final ScriptType outputScriptType) {
83-
if (outputScriptType == Script.ScriptType.P2PKH)
84-
return LegacyAddress.fromKey(params, key);
83+
if (outputScriptType == Script.ScriptType.P2PKH || outputScriptType == ScriptType.P2SH_P2WPKH)
84+
return LegacyAddress.fromKey(params, key, outputScriptType);
8585
else if (outputScriptType == Script.ScriptType.P2WPKH)
8686
return SegwitAddress.fromKey(params, key);
8787
else

core/src/main/java/org/bitcoinj/core/ECKey.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1295,7 +1295,7 @@ public void formatKeyWithAddress(boolean includePrivateKeys, @Nullable KeyParame
12951295
if (outputScriptType != null) {
12961296
builder.append(Address.fromKey(params, this, outputScriptType));
12971297
} else {
1298-
builder.append(LegacyAddress.fromKey(params, this));
1298+
builder.append(LegacyAddress.fromKey(params, this, Script.ScriptType.P2PKH));
12991299
if (isCompressed())
13001300
builder.append(',').append(SegwitAddress.fromKey(params, this));
13011301
}

core/src/main/java/org/bitcoinj/core/LegacyAddress.java

+13-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525

2626
import com.google.common.primitives.UnsignedBytes;
2727
import org.bitcoinj.params.Networks;
28+
import org.bitcoinj.script.Script;
2829
import org.bitcoinj.script.Script.ScriptType;
30+
import org.bitcoinj.script.ScriptBuilder;
31+
import org.bitcoinj.script.ScriptPattern;
2932

3033
/**
3134
* <p>A Bitcoin address looks like 1MsScoe2fTJoq4ZPdQgqyhgWeoNamYPevy and is derived from an elliptic curve public key
@@ -50,7 +53,7 @@ public class LegacyAddress extends Address {
5053
/**
5154
* Private constructor. Use {@link #fromBase58(NetworkParameters, String)},
5255
* {@link #fromPubKeyHash(NetworkParameters, byte[])}, {@link #fromScriptHash(NetworkParameters, byte[])} or
53-
* {@link #fromKey(NetworkParameters, ECKey)}.
56+
* {@link #fromKey(NetworkParameters, ECKey, ScriptType)}.
5457
*
5558
* @param params
5659
* network this address is valid for
@@ -91,8 +94,15 @@ public static LegacyAddress fromPubKeyHash(NetworkParameters params, byte[] hash
9194
* only the public part is used
9295
* @return constructed address
9396
*/
94-
public static LegacyAddress fromKey(NetworkParameters params, ECKey key) {
95-
return fromPubKeyHash(params, key.getPubKeyHash());
97+
public static LegacyAddress fromKey(NetworkParameters params, ECKey key, ScriptType outputScriptType) {
98+
if(outputScriptType == ScriptType.P2PKH) {
99+
return fromPubKeyHash(params, key.getPubKeyHash());
100+
} else if(outputScriptType == ScriptType.P2SH_P2WPKH) {
101+
Script script = ScriptBuilder.createP2SHP2WPKHOutputScript(key);
102+
return fromScriptHash(params, ScriptPattern.extractHashFromP2SH(script));
103+
} else {
104+
throw new IllegalArgumentException("Prohibited output script type: " + outputScriptType);
105+
}
96106
}
97107

98108
/**

core/src/main/java/org/bitcoinj/core/NetworkParameters.java

+13
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public abstract class NetworkParameters {
7979
protected int bip32HeaderP2PKHpriv;
8080
protected int bip32HeaderP2WPKHpub;
8181
protected int bip32HeaderP2WPKHpriv;
82+
protected int bip32HeaderP2SHP2WPKHpub;
83+
protected int bip32HeaderP2SHP2WPKHpriv;
8284

8385
/** Used to check majorities for block version upgrade */
8486
protected int majorityEnforceBlockUpgrade;
@@ -347,6 +349,17 @@ public int getBip32HeaderP2WPKHpub() {
347349
public int getBip32HeaderP2WPKHpriv() {
348350
return bip32HeaderP2WPKHpriv;
349351
}
352+
353+
/** Returns the 4 byte header for BIP32 wallet P2SH-P2WPKH - public key part. */
354+
public int getBip32HeaderP2SHP2WPKHpub() {
355+
return bip32HeaderP2SHP2WPKHpub;
356+
}
357+
358+
/** Returns the 4 byte header for BIP32 wallet P2SH-P2WPKH - private key part. */
359+
public int getBip32HeaderP2SHP2WPKHpriv() {
360+
return bip32HeaderP2SHP2WPKHpriv;
361+
}
362+
350363
/**
351364
* Returns the number of coins that will be produced in total, on this
352365
* network. Where not applicable, a very large number of coins is returned

core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,17 @@ public ECKey getConnectedKey(KeyBag keyBag) throws ScriptException {
147147
} else if (ScriptPattern.isP2WPKH(connectedScript)) {
148148
byte[] addressBytes = ScriptPattern.extractHashFromP2WH(connectedScript);
149149
return keyBag.findKeyFromPubKeyHash(addressBytes, Script.ScriptType.P2WPKH);
150-
} else if (ScriptPattern.isP2PK(connectedScript)) {
150+
} else if (ScriptPattern.isP2SH(connectedScript)) {
151+
byte[] addressBytes = ScriptPattern.extractHashFromP2SH(connectedScript);
152+
RedeemData redeemData = keyBag.findRedeemDataFromScriptHash(addressBytes);
153+
if(redeemData != null) {
154+
Script redeemScript = redeemData.redeemScript;
155+
byte[] witnessHash = ScriptPattern.extractHashFromP2WH(redeemScript);
156+
return keyBag.findKeyFromPubKeyHash(witnessHash, Script.ScriptType.P2SH_P2WPKH);
157+
} else {
158+
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Could not understand form of connected output script: " + connectedScript);
159+
}
160+
}else if (ScriptPattern.isP2PK(connectedScript)) {
151161
byte[] pubkeyBytes = ScriptPattern.extractKeyFromP2PK(connectedScript);
152162
return keyBag.findKeyFromPubKey(pubkeyBytes);
153163
} else {

core/src/main/java/org/bitcoinj/crypto/BIP38PrivateKey.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.bitcoinj.crypto;
1818

1919
import org.bitcoinj.core.*;
20+
import org.bitcoinj.script.Script;
2021
import org.bouncycastle.crypto.generators.SCrypt;
2122

2223
import com.google.common.primitives.Bytes;
@@ -118,7 +119,7 @@ public String toBase58() {
118119
public ECKey decrypt(String passphrase) throws BadPassphraseException {
119120
String normalizedPassphrase = Normalizer.normalize(passphrase, Normalizer.Form.NFC);
120121
ECKey key = ecMultiply ? decryptEC(normalizedPassphrase) : decryptNoEC(normalizedPassphrase);
121-
Sha256Hash hash = Sha256Hash.twiceOf(LegacyAddress.fromKey(params, key).toString().getBytes(StandardCharsets.US_ASCII));
122+
Sha256Hash hash = Sha256Hash.twiceOf(LegacyAddress.fromKey(params, key, Script.ScriptType.P2PKH).toString().getBytes(StandardCharsets.US_ASCII));
122123
byte[] actualAddressHash = Arrays.copyOfRange(hash.getBytes(), 0, 4);
123124
if (!Arrays.equals(actualAddressHash, addressHash))
124125
throw new BadPassphraseException();

core/src/main/java/org/bitcoinj/crypto/DeterministicKey.java

+2
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@ private byte[] serialize(NetworkParameters params, boolean pub, Script.ScriptTyp
487487
ser.putInt(pub ? params.getBip32HeaderP2PKHpub() : params.getBip32HeaderP2PKHpriv());
488488
else if (outputScriptType == Script.ScriptType.P2WPKH)
489489
ser.putInt(pub ? params.getBip32HeaderP2WPKHpub() : params.getBip32HeaderP2WPKHpriv());
490+
else if (outputScriptType == Script.ScriptType.P2SH_P2WPKH)
491+
ser.putInt(pub ? params.getBip32HeaderP2SHP2WPKHpub() : params.getBip32HeaderP2SHP2WPKHpriv());
490492
else
491493
throw new IllegalStateException(outputScriptType.toString());
492494
ser.put((byte) getDepth());

core/src/main/java/org/bitcoinj/params/MainNetParams.java

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public MainNetParams() {
4747
bip32HeaderP2PKHpriv = 0x019d9cfe; // The 4 byte header that serializes in base58 to "Ltpv"
4848
bip32HeaderP2WPKHpub = 0x04b24746; // The 4 byte header that serializes in base58 to "zpub".
4949
bip32HeaderP2WPKHpriv = 0x04b2430c; // The 4 byte header that serializes in base58 to "zprv"
50+
bip32HeaderP2SHP2WPKHpub = 0x049d7cb2; // The 4 byte header that serializes in base58 to "ypub"
51+
bip32HeaderP2SHP2WPKHpriv = 0x049d7878; // The 4 byte header that serializes in base58 to "yprv"
5052

5153
majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
5254
majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED;

core/src/main/java/org/bitcoinj/params/TestNet3Params.java

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ public TestNet3Params() {
7373
bip32HeaderP2PKHpriv = 0x04358394; // The 4 byte header that serializes in base58 to "tprv"
7474
bip32HeaderP2WPKHpub = 0x045f1cf6; // The 4 byte header that serializes in base58 to "vpub".
7575
bip32HeaderP2WPKHpriv = 0x045f18bc; // The 4 byte header that serializes in base58 to "vprv"
76+
bip32HeaderP2SHP2WPKHpub = 0x044a5262; // The 4 byte header that serializes in base58 to "upub".
77+
bip32HeaderP2SHP2WPKHpriv = 0x044a4e28; // The 4 byte header that serializes in base58 to "uprv"
7678

7779
majorityEnforceBlockUpgrade = TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
7880
majorityRejectBlockOutdated = TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED;

core/src/main/java/org/bitcoinj/script/Script.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ public enum ScriptType {
5959
P2PK(2), // pay to pubkey
6060
P2SH(3), // pay to script hash
6161
P2WPKH(4), // pay to witness pubkey hash
62-
P2WSH(5); // pay to witness script hash
62+
P2WSH(5), // pay to witness script hash
63+
P2SH_P2WPKH(6);
6364

6465
public final int id;
6566

@@ -280,7 +281,7 @@ public Address getToAddress(NetworkParameters params, boolean forcePayToPubKey)
280281
else if (ScriptPattern.isP2SH(this))
281282
return LegacyAddress.fromScriptHash(params, ScriptPattern.extractHashFromP2SH(this));
282283
else if (forcePayToPubKey && ScriptPattern.isP2PK(this))
283-
return LegacyAddress.fromKey(params, ECKey.fromPublicOnly(ScriptPattern.extractKeyFromP2PK(this)));
284+
return LegacyAddress.fromKey(params, ECKey.fromPublicOnly(ScriptPattern.extractKeyFromP2PK(this)), ScriptType.P2PKH);
284285
else if (ScriptPattern.isP2WH(this))
285286
return SegwitAddress.fromHash(params, ScriptPattern.extractHashFromP2WH(this));
286287
else

core/src/main/java/org/bitcoinj/script/ScriptBuilder.java

+26
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,32 @@ public static Script createP2WPKHOutputScript(ECKey key) {
477477
return createP2WPKHOutputScript(key.getPubKeyHash());
478478
}
479479

480+
/**
481+
* Creates a segwit scriptPubKey that sends to the given public key hash.
482+
*/
483+
public static Script createP2SHP2WPKHOutputScript(byte[] hash) {
484+
checkArgument(hash.length == SegwitAddress.WITNESS_PROGRAM_LENGTH_PKH);
485+
Script p2wpkhRedeemScript = createP2WPKHOutputScript(hash);
486+
return ScriptBuilder.createP2SHOutputScript(p2wpkhRedeemScript);
487+
}
488+
489+
/**
490+
* Creates a segwit scriptPubKey that sends to the given public key.
491+
*/
492+
public static Script createP2SHP2WPKHOutputScript(ECKey key) {
493+
checkArgument(key.isCompressed());
494+
Script p2wpkhRedeemScript = createP2WPKHOutputScript(key);
495+
return ScriptBuilder.createP2SHOutputScript(p2wpkhRedeemScript);
496+
}
497+
498+
/**
499+
* Creates a segwit scriptPubKey that sends to the given public key.
500+
*/
501+
public static Script createP2SHP2WPKHRedeemScript(ECKey key) {
502+
checkArgument(key.isCompressed());
503+
return createP2WPKHOutputScript(key);
504+
}
505+
480506
/**
481507
* Creates a scriptPubKey that sends to the given script hash. Read
482508
* <a href="https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki">BIP 16</a> to learn more about this

core/src/main/java/org/bitcoinj/signers/LocalTransactionSigner.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public boolean signInputs(ProposedTransaction propTx, KeyBag keyBag) {
116116
byte[] script = redeemData.redeemScript.getProgram();
117117
try {
118118
if (ScriptPattern.isP2PK(scriptPubKey) || ScriptPattern.isP2PKH(scriptPubKey)
119-
|| ScriptPattern.isP2SH(scriptPubKey)) {
119+
|| ScriptPattern.isP2SH(scriptPubKey) && !ScriptPattern.isP2WPKH(redeemData.redeemScript)) {
120120
TransactionSignature signature = tx.calculateSignature(i, key, script, Transaction.SigHash.ALL,
121121
false);
122122

@@ -140,6 +140,14 @@ public boolean signInputs(ProposedTransaction propTx, KeyBag keyBag) {
140140
Transaction.SigHash.ALL, false);
141141
txIn.setScriptSig(ScriptBuilder.createEmpty());
142142
txIn.setWitness(TransactionWitness.redeemP2WPKH(signature, key));
143+
} else if(ScriptPattern.isP2SH(scriptPubKey) && ScriptPattern.isP2WPKH(redeemData.redeemScript)) {
144+
Script redeemScript = ScriptBuilder.createP2WPKHOutputScript(key);
145+
Script witnessScript = ScriptBuilder.createP2PKHOutputScript(key);
146+
Coin value = txIn.getValue();
147+
TransactionSignature signature = tx.calculateWitnessSignature(i, key, witnessScript, value,
148+
Transaction.SigHash.ALL, false);
149+
txIn.setScriptSig(new ScriptBuilder().data(redeemScript.getProgram()).build());
150+
txIn.setWitness(TransactionWitness.redeemP2WPKH(signature, key));
143151
} else {
144152
throw new IllegalStateException(script.toString());
145153
}

core/src/main/java/org/bitcoinj/store/DatabaseFullPrunedBlockStore.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,7 @@ public List<UTXO> getOpenTransactionOutputs(List<ECKey> keys) throws UTXOProvide
11331133
s = conn.get().prepareStatement(getTransactionOutputSelectSQL());
11341134
for (ECKey key : keys) {
11351135
// TODO switch to pubKeyHash in order to support native segwit addresses
1136-
s.setString(1, LegacyAddress.fromKey(params, key).toString());
1136+
s.setString(1, LegacyAddress.fromKey(params, key, ScriptType.P2PKH).toString());
11371137
ResultSet rs = s.executeQuery();
11381138
while (rs.next()) {
11391139
Sha256Hash hash = Sha256Hash.wrap(rs.getBytes(1));

core/src/main/java/org/bitcoinj/store/MemoryFullPrunedBlockStore.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.bitcoinj.core.*;
2020
import com.google.common.base.Preconditions;
21+
import org.bitcoinj.script.Script;
2122

2223
import javax.annotation.Nullable;
2324
import java.util.*;
@@ -416,7 +417,7 @@ public List<UTXO> getOpenTransactionOutputs(List<ECKey> keys) throws UTXOProvide
416417
for (UTXO output : outputsList) {
417418
for (ECKey key : keys) {
418419
// TODO switch to pubKeyHash in order to support native segwit addresses
419-
Address address = LegacyAddress.fromKey(params, key);
420+
Address address = LegacyAddress.fromKey(params, key, Script.ScriptType.P2PKH);
420421
if (output.getAddress().equals(address.toString())) {
421422
foundOutputs.add(output);
422423
}

core/src/main/java/org/bitcoinj/wallet/DefaultKeyChainFactory.java

+6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public DeterministicKeyChain makeKeyChain(DeterministicSeed seed, KeyCrypter cry
3232
DeterministicKeyChain chain;
3333
if (isMarried)
3434
chain = new MarriedKeyChain(seed, crypter, outputScriptType, accountPath);
35+
else if(outputScriptType == Script.ScriptType.P2SH_P2WPKH)
36+
chain = new NestedSegwitKeyChain(seed, crypter, outputScriptType, accountPath);
3537
else
3638
chain = new DeterministicKeyChain(seed, crypter, outputScriptType, accountPath);
3739
return chain;
@@ -45,6 +47,8 @@ public DeterministicKeyChain makeWatchingKeyChain(DeterministicKey accountKey, b
4547
chain = new MarriedKeyChain(accountKey, outputScriptType);
4648
else if (isFollowingKey)
4749
chain = DeterministicKeyChain.builder().watchAndFollow(accountKey).outputScriptType(outputScriptType).build();
50+
else if(outputScriptType == Script.ScriptType.P2SH_P2WPKH)
51+
chain = NestedSegwitKeyChain.builder().watch(accountKey).outputScriptType(outputScriptType).build();
4852
else
4953
chain = DeterministicKeyChain.builder().watch(accountKey).outputScriptType(outputScriptType).build();
5054
return chain;
@@ -56,6 +60,8 @@ public DeterministicKeyChain makeSpendingKeyChain(DeterministicKey accountKey, b
5660
DeterministicKeyChain chain;
5761
if (isMarried)
5862
chain = new MarriedKeyChain(accountKey, outputScriptType);
63+
else if (outputScriptType == Script.ScriptType.P2SH_P2WPKH)
64+
chain = NestedSegwitKeyChain.builder().spend(accountKey).outputScriptType(outputScriptType).build();
5965
else
6066
chain = DeterministicKeyChain.builder().spend(accountKey).outputScriptType(outputScriptType).build();
6167
return chain;

core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
117117
// m / 44' / 2' / 0'
118118
public static final HDPath BIP44_ACCOUNT_ZERO_PATH = HDPath.M(new ChildNumber(44, true))
119119
.extend(new ChildNumber(2, true), ChildNumber.ZERO_HARDENED);
120+
// m / 49' / 2' / 0'
121+
public static final HDPath BIP49_ACCOUNT_ZERO_PATH = HDPath.M(new ChildNumber(49, true))
122+
.extend(new ChildNumber(2, true), ChildNumber.ZERO_HARDENED);
120123
// m / 44' / 2' / 0'
121124
public static final HDPath BIP84_ACCOUNT_ZERO_PATH = HDPath.M(new ChildNumber(84, true))
122125
.extend(new ChildNumber(2, true), ChildNumber.ZERO_HARDENED);
@@ -363,7 +366,8 @@ public DeterministicKeyChain(DeterministicKey key, boolean isFollowing, boolean
363366
protected DeterministicKeyChain(DeterministicSeed seed, @Nullable KeyCrypter crypter,
364367
Script.ScriptType outputScriptType, List<ChildNumber> accountPath) {
365368
checkArgument(outputScriptType == null || outputScriptType == Script.ScriptType.P2PKH
366-
|| outputScriptType == Script.ScriptType.P2WPKH, "Only P2PKH or P2WPKH allowed.");
369+
|| outputScriptType == Script.ScriptType.P2WPKH
370+
|| outputScriptType == Script.ScriptType.P2SH_P2WPKH, "Only P2PKH or P2WPKH allowed.");
367371
this.outputScriptType = outputScriptType != null ? outputScriptType : Script.ScriptType.P2PKH;
368372
this.accountPath = HDPath.M(accountPath);
369373
this.seed = seed;
@@ -1361,6 +1365,10 @@ public boolean isMarried() {
13611365
return false;
13621366
}
13631367

1368+
public boolean isNestedSegwit() {
1369+
return false;
1370+
}
1371+
13641372
/** Get redeem data for a key. Only applicable to married keychains. */
13651373
public RedeemData getRedeemData(DeterministicKey followedKey) {
13661374
throw new UnsupportedOperationException();

0 commit comments

Comments
 (0)