mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-02-23 14:40:40 +01:00
Script, SegwitAddress: detect taproot scripts and decode them into addresses
This commit is contained in:
parent
559f6a2711
commit
4b243a615a
5 changed files with 69 additions and 10 deletions
|
@ -47,6 +47,7 @@ import org.bitcoinj.script.Script;
|
|||
public class SegwitAddress extends Address {
|
||||
public static final int WITNESS_PROGRAM_LENGTH_PKH = 20;
|
||||
public static final int WITNESS_PROGRAM_LENGTH_SH = 32;
|
||||
public static final int WITNESS_PROGRAM_LENGTH_TR = 32;
|
||||
public static final int WITNESS_PROGRAM_MIN_LENGTH = 2;
|
||||
public static final int WITNESS_PROGRAM_MAX_LENGTH = 40;
|
||||
|
||||
|
@ -59,7 +60,7 @@ public class SegwitAddress extends Address {
|
|||
* @param witnessVersion
|
||||
* version number between 0 and 16
|
||||
* @param witnessProgram
|
||||
* hash of pubkey or script (for version 0)
|
||||
* hash of pubkey, pubkey or script (depending on version)
|
||||
*/
|
||||
private SegwitAddress(NetworkParameters params, int witnessVersion, byte[] witnessProgram)
|
||||
throws AddressFormatException {
|
||||
|
@ -138,13 +139,20 @@ public class SegwitAddress extends Address {
|
|||
@Override
|
||||
public Script.ScriptType getOutputScriptType() {
|
||||
int version = getWitnessVersion();
|
||||
checkState(version == 0);
|
||||
int programLength = getWitnessProgram().length;
|
||||
if (programLength == WITNESS_PROGRAM_LENGTH_PKH)
|
||||
return Script.ScriptType.P2WPKH;
|
||||
if (programLength == WITNESS_PROGRAM_LENGTH_SH)
|
||||
return Script.ScriptType.P2WSH;
|
||||
throw new IllegalStateException("Cannot happen.");
|
||||
if (version == 0) {
|
||||
int programLength = getWitnessProgram().length;
|
||||
if (programLength == WITNESS_PROGRAM_LENGTH_PKH)
|
||||
return Script.ScriptType.P2WPKH;
|
||||
if (programLength == WITNESS_PROGRAM_LENGTH_SH)
|
||||
return Script.ScriptType.P2WSH;
|
||||
throw new IllegalStateException(); // cannot happen
|
||||
} else if (version == 1) {
|
||||
int programLength = getWitnessProgram().length;
|
||||
if (programLength == WITNESS_PROGRAM_LENGTH_TR)
|
||||
return Script.ScriptType.P2TR;
|
||||
throw new IllegalStateException(); // cannot happen
|
||||
}
|
||||
throw new IllegalStateException("cannot handle: " + version);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -202,6 +210,23 @@ public class SegwitAddress extends Address {
|
|||
return new SegwitAddress(params, 0, hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link SegwitAddress} that represents the given program, which is either a pubkey, a pubkey hash
|
||||
* or a script hash – depending on the script version. The resulting address will be either a P2WPKH, a P2WSH or
|
||||
* a P2TR type of address.
|
||||
*
|
||||
* @param params
|
||||
* network this address is valid for
|
||||
* @param witnessVersion
|
||||
* version number between 0 and 16
|
||||
* @param witnessProgram
|
||||
* version dependent witness program
|
||||
* @return constructed address
|
||||
*/
|
||||
public static SegwitAddress fromProgram(NetworkParameters params, int witnessVersion, byte[] witnessProgram) {
|
||||
return new SegwitAddress(params, witnessVersion, witnessProgram);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link SegwitAddress} that represents the public part of the given {@link ECKey}. Note that an
|
||||
* address is derived from a hash of the public key and is not the public key itself.
|
||||
|
|
|
@ -59,7 +59,8 @@ public class Script {
|
|||
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
|
||||
P2TR(6); // pay to taproot
|
||||
|
||||
public final int id;
|
||||
|
||||
|
@ -283,6 +284,8 @@ public class Script {
|
|||
return LegacyAddress.fromKey(params, ECKey.fromPublicOnly(ScriptPattern.extractKeyFromP2PK(this)));
|
||||
else if (ScriptPattern.isP2WH(this))
|
||||
return SegwitAddress.fromHash(params, ScriptPattern.extractHashFromP2WH(this));
|
||||
else if (ScriptPattern.isP2TR(this))
|
||||
return SegwitAddress.fromProgram(params, 1, ScriptPattern.extractOutputKeyFromP2TR(this));
|
||||
else
|
||||
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Cannot cast this script to an address");
|
||||
}
|
||||
|
@ -1676,6 +1679,8 @@ public class Script {
|
|||
return ScriptType.P2WPKH;
|
||||
if (ScriptPattern.isP2WSH(this))
|
||||
return ScriptType.P2WSH;
|
||||
if (ScriptPattern.isP2TR(this))
|
||||
return ScriptType.P2TR;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -196,6 +196,32 @@ public class ScriptPattern {
|
|||
return script.chunks.get(1).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this script is of the form {@code OP_1 <pubkey>}. This is a P2TR scriptPubKey. This
|
||||
* script type was introduced with taproot.
|
||||
*/
|
||||
public static boolean isP2TR(Script script) {
|
||||
List<ScriptChunk> chunks = script.chunks;
|
||||
if (chunks.size() != 2)
|
||||
return false;
|
||||
if (!chunks.get(0).equalsOpCode(OP_1))
|
||||
return false;
|
||||
byte[] chunk1data = chunks.get(1).data;
|
||||
if (chunk1data == null)
|
||||
return false;
|
||||
if (chunk1data.length != SegwitAddress.WITNESS_PROGRAM_LENGTH_TR)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the taproot output key from a P2TR scriptPubKey. It's important that the script is in the correct
|
||||
* form, so you will want to guard calls to this method with {@link #isP2TR(Script)}.
|
||||
*/
|
||||
public static byte[] extractOutputKeyFromP2TR(Script script) {
|
||||
return script.chunks.get(1).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this script matches the format used for m-of-n multisig outputs:
|
||||
* {@code [m] [keys...] [n] CHECKMULTISIG}
|
||||
|
|
|
@ -1162,7 +1162,7 @@ public class Wallet extends BaseTaggableObject
|
|||
return isPubKeyHashMine(address.getHash(), scriptType);
|
||||
else if (scriptType == ScriptType.P2SH)
|
||||
return isPayToScriptHashMine(address.getHash());
|
||||
else if (scriptType == ScriptType.P2WSH)
|
||||
else if (scriptType == ScriptType.P2WSH || scriptType == ScriptType.P2TR)
|
||||
return false;
|
||||
else
|
||||
throw new IllegalArgumentException(address.toString());
|
||||
|
|
|
@ -462,6 +462,9 @@ public class ScriptTest {
|
|||
Script p2wshScript = ScriptBuilder.createP2WSHOutputScript(new byte[32]);
|
||||
scriptAddress = SegwitAddress.fromHash(TESTNET, ScriptPattern.extractHashFromP2WH(p2wshScript));
|
||||
assertEquals(scriptAddress, p2wshScript.getToAddress(TESTNET));
|
||||
// P2TR
|
||||
toAddress = SegwitAddress.fromProgram(TESTNET, 1, new byte[32]);
|
||||
assertEquals(toAddress, ScriptBuilder.createOutputScript(toAddress).getToAddress(TESTNET));
|
||||
}
|
||||
|
||||
@Test(expected = ScriptException.class)
|
||||
|
|
Loading…
Add table
Reference in a new issue