mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-12 18:25:51 +00:00
Determine native segwit destination addresses of a transaction by parsing the segwit scriptPubKey.
This commit is contained in:
parent
52afdc629e
commit
061f5c9841
@ -131,7 +131,7 @@ public class TransactionOutput extends ChildMessage {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public LegacyAddress getAddressFromP2PKHScript(NetworkParameters networkParameters) throws ScriptException{
|
public LegacyAddress getAddressFromP2PKHScript(NetworkParameters networkParameters) throws ScriptException{
|
||||||
if (ScriptPattern.isPayToPubKeyHash(getScriptPubKey()))
|
if (ScriptPattern.isPayToPubKeyHash(getScriptPubKey()))
|
||||||
return getScriptPubKey().getToAddress(networkParameters);
|
return (LegacyAddress) getScriptPubKey().getToAddress(networkParameters);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ public class TransactionOutput extends ChildMessage {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public LegacyAddress getAddressFromP2SH(NetworkParameters networkParameters) throws ScriptException{
|
public LegacyAddress getAddressFromP2SH(NetworkParameters networkParameters) throws ScriptException{
|
||||||
if (ScriptPattern.isPayToScriptHash(getScriptPubKey()))
|
if (ScriptPattern.isPayToScriptHash(getScriptPubKey()))
|
||||||
return getScriptPubKey().getToAddress(networkParameters);
|
return (LegacyAddress) getScriptPubKey().getToAddress(networkParameters);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -239,22 +239,17 @@ public class Script {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>If a program matches the standard template DUP HASH160 <pubkey hash> EQUALVERIFY CHECKSIG
|
* <p>If the program somehow pays to a hash, returns the hash.</p>
|
||||||
* then this function retrieves the third element.
|
|
||||||
* In this case, this is useful for fetching the destination address of a transaction.</p>
|
|
||||||
*
|
*
|
||||||
* <p>If a program matches the standard template HASH160 <script hash> EQUAL
|
* <p>Otherwise this method throws a ScriptException.</p>
|
||||||
* then this function retrieves the second element.
|
|
||||||
* In this case, this is useful for fetching the hash of the redeem script of a transaction.</p>
|
|
||||||
*
|
|
||||||
* <p>Otherwise it throws a ScriptException.</p>
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public byte[] getPubKeyHash() throws ScriptException {
|
public byte[] getPubKeyHash() throws ScriptException {
|
||||||
if (ScriptPattern.isPayToPubKeyHash(this))
|
if (ScriptPattern.isPayToPubKeyHash(this))
|
||||||
return ScriptPattern.extractHashFromPayToPubKeyHash(this);
|
return ScriptPattern.extractHashFromPayToPubKeyHash(this);
|
||||||
else if (ScriptPattern.isPayToScriptHash(this))
|
else if (ScriptPattern.isPayToScriptHash(this))
|
||||||
return ScriptPattern.extractHashFromPayToScriptHash(this);
|
return ScriptPattern.extractHashFromPayToScriptHash(this);
|
||||||
|
else if (ScriptPattern.isPayToWitnessHash(this))
|
||||||
|
return ScriptPattern.extractHashFromPayToWitnessHash(this);
|
||||||
else
|
else
|
||||||
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not in the standard scriptPubKey form");
|
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not in the standard scriptPubKey form");
|
||||||
}
|
}
|
||||||
@ -283,7 +278,7 @@ public class Script {
|
|||||||
/**
|
/**
|
||||||
* Gets the destination address from this script, if it's in the required form.
|
* Gets the destination address from this script, if it's in the required form.
|
||||||
*/
|
*/
|
||||||
public LegacyAddress getToAddress(NetworkParameters params) throws ScriptException {
|
public Address getToAddress(NetworkParameters params) throws ScriptException {
|
||||||
return getToAddress(params, false);
|
return getToAddress(params, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,13 +289,15 @@ public class Script {
|
|||||||
* If true, allow payToPubKey to be casted to the corresponding address. This is useful if you prefer
|
* If true, allow payToPubKey to be casted to the corresponding address. This is useful if you prefer
|
||||||
* showing addresses rather than pubkeys.
|
* showing addresses rather than pubkeys.
|
||||||
*/
|
*/
|
||||||
public LegacyAddress getToAddress(NetworkParameters params, boolean forcePayToPubKey) throws ScriptException {
|
public Address getToAddress(NetworkParameters params, boolean forcePayToPubKey) throws ScriptException {
|
||||||
if (ScriptPattern.isPayToPubKeyHash(this))
|
if (ScriptPattern.isPayToPubKeyHash(this))
|
||||||
return LegacyAddress.fromPubKeyHash(params, ScriptPattern.extractHashFromPayToPubKeyHash(this));
|
return LegacyAddress.fromPubKeyHash(params, ScriptPattern.extractHashFromPayToPubKeyHash(this));
|
||||||
else if (ScriptPattern.isPayToScriptHash(this))
|
else if (ScriptPattern.isPayToScriptHash(this))
|
||||||
return LegacyAddress.fromP2SHScript(params, this);
|
return LegacyAddress.fromP2SHScript(params, this);
|
||||||
else if (forcePayToPubKey && ScriptPattern.isPayToPubKey(this))
|
else if (forcePayToPubKey && ScriptPattern.isPayToPubKey(this))
|
||||||
return LegacyAddress.fromKey(params, ECKey.fromPublicOnly(ScriptPattern.extractKeyFromPayToPubKey(this)));
|
return LegacyAddress.fromKey(params, ECKey.fromPublicOnly(ScriptPattern.extractKeyFromPayToPubKey(this)));
|
||||||
|
else if (ScriptPattern.isPayToWitnessHash(this))
|
||||||
|
return SegwitAddress.fromHash(params, ScriptPattern.extractHashFromPayToWitnessHash(this));
|
||||||
else
|
else
|
||||||
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Cannot cast this script to a pay-to-address type");
|
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Cannot cast this script to a pay-to-address type");
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package org.bitcoinj.script;
|
package org.bitcoinj.script;
|
||||||
|
|
||||||
import org.bitcoinj.core.LegacyAddress;
|
import org.bitcoinj.core.LegacyAddress;
|
||||||
|
import org.bitcoinj.core.SegwitAddress;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -105,6 +106,34 @@ public class ScriptPattern {
|
|||||||
return script.chunks.get(0).data;
|
return script.chunks.get(0).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this script is of the form OP_0 <hash>. This can either be a P2WPKH or P2WSH scriptPubKey. These
|
||||||
|
* two script types were introduced with segwit.
|
||||||
|
*/
|
||||||
|
public static boolean isPayToWitnessHash(Script script) {
|
||||||
|
List<ScriptChunk> chunks = script.chunks;
|
||||||
|
if (chunks.size() != 2)
|
||||||
|
return false;
|
||||||
|
if (!chunks.get(0).equalsOpCode(OP_0))
|
||||||
|
return false;
|
||||||
|
byte[] chunk1data = chunks.get(1).data;
|
||||||
|
if (chunk1data == null)
|
||||||
|
return false;
|
||||||
|
if (chunk1data.length != SegwitAddress.WITNESS_PROGRAM_LENGTH_PKH
|
||||||
|
&& chunk1data.length != SegwitAddress.WITNESS_PROGRAM_LENGTH_SH)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the pubkey hash from a P2WPKH or the script hash from a P2WSH scriptPubKey. It's important that the
|
||||||
|
* script is in the correct form, so you will want to guard calls to this method with
|
||||||
|
* {@link #isPayToWitnessHash(Script)}.
|
||||||
|
*/
|
||||||
|
public static byte[] extractHashFromPayToWitnessHash(Script script) {
|
||||||
|
return script.chunks.get(1).data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this script matches the format used for multisig outputs: [n] [keys...] [m] CHECKMULTISIG
|
* Returns whether this script matches the format used for multisig outputs: [n] [keys...] [m] CHECKMULTISIG
|
||||||
*/
|
*/
|
||||||
|
@ -29,6 +29,7 @@ import java.io.*;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.bitcoinj.core.LegacyAddress;
|
import org.bitcoinj.core.LegacyAddress;
|
||||||
|
import org.bitcoinj.core.Address;
|
||||||
import org.bitcoinj.core.AddressFormatException;
|
import org.bitcoinj.core.AddressFormatException;
|
||||||
import org.bitcoinj.core.ECKey;
|
import org.bitcoinj.core.ECKey;
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
@ -461,7 +462,7 @@ public class LevelDBFullPrunedBlockStore implements FullPrunedBlockStore {
|
|||||||
}
|
}
|
||||||
if (txout != null) {
|
if (txout != null) {
|
||||||
Script sc = txout.getScript();
|
Script sc = txout.getScript();
|
||||||
LegacyAddress address = sc.getToAddress(params, true);
|
Address address = sc.getToAddress(params, true);
|
||||||
UTXO output = new UTXO(txout.getHash(), txout.getIndex(), txout.getValue(), txout.getHeight(),
|
UTXO output = new UTXO(txout.getHash(), txout.getIndex(), txout.getValue(), txout.getHeight(),
|
||||||
txout.isCoinbase(), txout.getScript(), address.toString());
|
txout.isCoinbase(), txout.getScript(), address.toString());
|
||||||
results.add(output);
|
results.add(output);
|
||||||
@ -887,7 +888,7 @@ public class LevelDBFullPrunedBlockStore implements FullPrunedBlockStore {
|
|||||||
String address = out.getAddress();
|
String address = out.getAddress();
|
||||||
if (address == null || address.equals("")) {
|
if (address == null || address.equals("")) {
|
||||||
Script sc = out.getScript();
|
Script sc = out.getScript();
|
||||||
a = sc.getToAddress(params);
|
a = (LegacyAddress) sc.getToAddress(params);
|
||||||
hashBytes = a.getHash();
|
hashBytes = a.getHash();
|
||||||
} else {
|
} else {
|
||||||
a = LegacyAddress.fromBase58(params, out.getAddress());
|
a = LegacyAddress.fromBase58(params, out.getAddress());
|
||||||
|
@ -124,8 +124,8 @@ public class KeyChainGroup implements KeyBag {
|
|||||||
|
|
||||||
if (isMarried()) {
|
if (isMarried()) {
|
||||||
for (Map.Entry<KeyChain.KeyPurpose, DeterministicKey> entry : this.currentKeys.entrySet()) {
|
for (Map.Entry<KeyChain.KeyPurpose, DeterministicKey> entry : this.currentKeys.entrySet()) {
|
||||||
LegacyAddress address = makeP2SHOutputScript(entry.getValue(), getActiveKeyChain()).getToAddress(params);
|
Address address = makeP2SHOutputScript(entry.getValue(), getActiveKeyChain()).getToAddress(params);
|
||||||
currentAddresses.put(entry.getKey(), address);
|
currentAddresses.put(entry.getKey(), (LegacyAddress) address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1005,7 +1005,7 @@ public class Wallet extends BaseTaggableObject
|
|||||||
List<LegacyAddress> addresses = new LinkedList<>();
|
List<LegacyAddress> addresses = new LinkedList<>();
|
||||||
for (Script script : watchedScripts)
|
for (Script script : watchedScripts)
|
||||||
if (ScriptPattern.isPayToPubKeyHash(script))
|
if (ScriptPattern.isPayToPubKeyHash(script))
|
||||||
addresses.add(script.getToAddress(params));
|
addresses.add(((LegacyAddress) script.getToAddress(params)));
|
||||||
return addresses;
|
return addresses;
|
||||||
} finally {
|
} finally {
|
||||||
keyChainGroupLock.unlock();
|
keyChainGroupLock.unlock();
|
||||||
|
@ -28,8 +28,10 @@ import java.util.Locale;
|
|||||||
|
|
||||||
import org.bitcoinj.params.MainNetParams;
|
import org.bitcoinj.params.MainNetParams;
|
||||||
import org.bitcoinj.params.TestNet3Params;
|
import org.bitcoinj.params.TestNet3Params;
|
||||||
|
import org.bitcoinj.script.Script;
|
||||||
import org.bitcoinj.script.Script.ScriptType;
|
import org.bitcoinj.script.Script.ScriptType;
|
||||||
import org.bitcoinj.script.ScriptBuilder;
|
import org.bitcoinj.script.ScriptBuilder;
|
||||||
|
import org.bitcoinj.script.ScriptPattern;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
@ -103,6 +105,11 @@ public class SegwitAddressTest {
|
|||||||
assertEquals(valid.expectedScriptPubKey,
|
assertEquals(valid.expectedScriptPubKey,
|
||||||
Utils.HEX.encode(ScriptBuilder.createOutputScript(address).getProgram()));
|
Utils.HEX.encode(ScriptBuilder.createOutputScript(address).getProgram()));
|
||||||
assertEquals(valid.address.toLowerCase(Locale.ROOT), address.toBech32());
|
assertEquals(valid.address.toLowerCase(Locale.ROOT), address.toBech32());
|
||||||
|
if (valid.expectedWitnessVersion == 0) {
|
||||||
|
Script expectedScriptPubKey = new Script(Utils.HEX.decode(valid.expectedScriptPubKey));
|
||||||
|
assertEquals(address, SegwitAddress.fromHash(valid.expectedParams,
|
||||||
|
ScriptPattern.extractHashFromPayToWitnessHash(expectedScriptPubKey)));
|
||||||
|
}
|
||||||
assertEquals(valid.expectedWitnessVersion, address.getWitnessVersion());
|
assertEquals(valid.expectedWitnessVersion, address.getWitnessVersion());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,10 @@ package org.bitcoinj.script;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import org.bitcoinj.core.LegacyAddress;
|
import org.bitcoinj.core.LegacyAddress;
|
||||||
import org.bitcoinj.core.ECKey;
|
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
|
import org.bitcoinj.core.SegwitAddress;
|
||||||
|
import org.bitcoinj.core.Sha256Hash;
|
||||||
|
import org.bitcoinj.core.ECKey;
|
||||||
import org.bitcoinj.params.MainNetParams;
|
import org.bitcoinj.params.MainNetParams;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@ -42,12 +44,18 @@ public class ScriptPatternTest {
|
|||||||
assertTrue(ScriptPattern.isPayToScriptHash(
|
assertTrue(ScriptPattern.isPayToScriptHash(
|
||||||
ScriptBuilder.createP2SHOutputScript(2, keys)
|
ScriptBuilder.createP2SHOutputScript(2, keys)
|
||||||
));
|
));
|
||||||
assertTrue(ScriptPattern.isSentToMultisig(
|
|
||||||
ScriptBuilder.createMultiSigOutputScript(2, keys)
|
|
||||||
));
|
|
||||||
assertTrue(ScriptPattern.isPayToPubKey(
|
assertTrue(ScriptPattern.isPayToPubKey(
|
||||||
ScriptBuilder.createOutputScript(keys.get(0))
|
ScriptBuilder.createOutputScript(keys.get(0))
|
||||||
));
|
));
|
||||||
|
assertTrue(ScriptPattern.isPayToWitnessHash(
|
||||||
|
ScriptBuilder.createOutputScript(SegwitAddress.fromHash(MAINNET, keys.get(0).getPubKeyHash()))
|
||||||
|
));
|
||||||
|
assertTrue(ScriptPattern.isPayToWitnessHash(
|
||||||
|
ScriptBuilder.createOutputScript(SegwitAddress.fromHash(MAINNET, Sha256Hash.hash(new byte[0])))
|
||||||
|
));
|
||||||
|
assertTrue(ScriptPattern.isSentToMultisig(
|
||||||
|
ScriptBuilder.createMultiSigOutputScript(2, keys)
|
||||||
|
));
|
||||||
assertTrue(ScriptPattern.isSentToCltvPaymentChannel(
|
assertTrue(ScriptPattern.isSentToCltvPaymentChannel(
|
||||||
ScriptBuilder.createCLTVPaymentChannelOutput(BigInteger.ONE, keys.get(0), keys.get(1))
|
ScriptBuilder.createCLTVPaymentChannelOutput(BigInteger.ONE, keys.get(0), keys.get(1))
|
||||||
));
|
));
|
||||||
|
@ -2928,7 +2928,7 @@ public class WalletTest extends TestWithWallet {
|
|||||||
assertEquals(THREE_CENTS, tx.getValueSentFromMe(wallet));
|
assertEquals(THREE_CENTS, tx.getValueSentFromMe(wallet));
|
||||||
assertEquals(THREE_CENTS.subtract(tx.getFee()), tx.getValueSentToMe(wallet));
|
assertEquals(THREE_CENTS.subtract(tx.getFee()), tx.getValueSentToMe(wallet));
|
||||||
// TX sends to one of our addresses (for now we ignore married wallets).
|
// TX sends to one of our addresses (for now we ignore married wallets).
|
||||||
final LegacyAddress toAddress = tx.getOutput(0).getScriptPubKey().getToAddress(UNITTEST);
|
final LegacyAddress toAddress = (LegacyAddress) tx.getOutput(0).getScriptPubKey().getToAddress(UNITTEST);
|
||||||
final ECKey rotatingToKey = wallet.findKeyFromPubHash(toAddress.getHash());
|
final ECKey rotatingToKey = wallet.findKeyFromPubHash(toAddress.getHash());
|
||||||
assertNotNull(rotatingToKey);
|
assertNotNull(rotatingToKey);
|
||||||
assertFalse(wallet.isKeyRotating(rotatingToKey));
|
assertFalse(wallet.isKeyRotating(rotatingToKey));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user