mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 01:56:44 +00:00
add psbt v2 support
This commit is contained in:
parent
4564c5d25a
commit
96df6284e1
5 changed files with 1074 additions and 64 deletions
|
@ -23,16 +23,21 @@ import static com.sparrowwallet.drongo.wallet.Wallet.addDummySpendingInput;
|
||||||
public class PSBT {
|
public class PSBT {
|
||||||
public static final byte PSBT_GLOBAL_UNSIGNED_TX = 0x00;
|
public static final byte PSBT_GLOBAL_UNSIGNED_TX = 0x00;
|
||||||
public static final byte PSBT_GLOBAL_BIP32_PUBKEY = 0x01;
|
public static final byte PSBT_GLOBAL_BIP32_PUBKEY = 0x01;
|
||||||
|
public static final byte PSBT_GLOBAL_TX_VERSION = 0x02;
|
||||||
|
public static final byte PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03;
|
||||||
|
public static final byte PSBT_GLOBAL_INPUT_COUNT = 0x04;
|
||||||
|
public static final byte PSBT_GLOBAL_OUTPUT_COUNT = 0x05;
|
||||||
|
public static final byte PSBT_GLOBAL_TX_MODIFIABLE = 0x06;
|
||||||
public static final byte PSBT_GLOBAL_VERSION = (byte)0xfb;
|
public static final byte PSBT_GLOBAL_VERSION = (byte)0xfb;
|
||||||
public static final byte PSBT_GLOBAL_PROPRIETARY = (byte)0xfc;
|
public static final byte PSBT_GLOBAL_PROPRIETARY = (byte)0xfc;
|
||||||
|
|
||||||
public static final String PSBT_MAGIC_HEX = "70736274";
|
public static final String PSBT_MAGIC_HEX = "70736274";
|
||||||
public static final int PSBT_MAGIC_INT = 1886610036;
|
public static final int PSBT_MAGIC_INT = 1886610036;
|
||||||
|
|
||||||
private static final int STATE_GLOBALS = 1;
|
public static final int STATE_GLOBALS = 1;
|
||||||
private static final int STATE_INPUTS = 2;
|
public static final int STATE_INPUTS = 2;
|
||||||
private static final int STATE_OUTPUTS = 3;
|
public static final int STATE_OUTPUTS = 3;
|
||||||
private static final int STATE_END = 4;
|
public static final int STATE_END = 4;
|
||||||
|
|
||||||
private int inputs = 0;
|
private int inputs = 0;
|
||||||
private int outputs = 0;
|
private int outputs = 0;
|
||||||
|
@ -44,20 +49,28 @@ public class PSBT {
|
||||||
private final Map<ExtendedKey, KeyDerivation> extendedPublicKeys = new LinkedHashMap<>();
|
private final Map<ExtendedKey, KeyDerivation> extendedPublicKeys = new LinkedHashMap<>();
|
||||||
private final Map<String, String> globalProprietary = new LinkedHashMap<>();
|
private final Map<String, String> globalProprietary = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
//PSBTv2 fields
|
||||||
|
private Long txVersion = null;
|
||||||
|
private Long fallbackLocktime = null;
|
||||||
|
private Long inputCount = null;
|
||||||
|
private Long outputCount = null;
|
||||||
|
private Byte modifiable = null;
|
||||||
|
|
||||||
private final List<PSBTInput> psbtInputs = new ArrayList<>();
|
private final List<PSBTInput> psbtInputs = new ArrayList<>();
|
||||||
private final List<PSBTOutput> psbtOutputs = new ArrayList<>();
|
private final List<PSBTOutput> psbtOutputs = new ArrayList<>();
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(PSBT.class);
|
private static final Logger log = LoggerFactory.getLogger(PSBT.class);
|
||||||
|
private boolean verifyPrevTxids = true;
|
||||||
|
|
||||||
public PSBT(Transaction transaction) {
|
public PSBT(Transaction transaction) {
|
||||||
this.transaction = transaction;
|
this.transaction = transaction;
|
||||||
|
|
||||||
for(int i = 0; i < transaction.getInputs().size(); i++) {
|
for(int i = 0; i < transaction.getInputs().size(); i++) {
|
||||||
psbtInputs.add(new PSBTInput(this, transaction, i));
|
psbtInputs.add(new PSBTInput(this, i));
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int i = 0; i < transaction.getOutputs().size(); i++) {
|
for(int i = 0; i < transaction.getOutputs().size(); i++) {
|
||||||
psbtOutputs.add(new PSBTOutput());
|
psbtOutputs.add(new PSBTOutput(this, i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +139,7 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PSBTInput psbtInput = new PSBTInput(this, signingWallet.getScriptType(), transaction, inputIndex, utxo, utxoIndex, redeemScript, witnessScript, derivedPublicKeys, Collections.emptyMap(), tapInternalKey, alwaysIncludeWitnessUtxo);
|
PSBTInput psbtInput = new PSBTInput(this, signingWallet.getScriptType(), inputIndex, utxo, utxoIndex, redeemScript, witnessScript, derivedPublicKeys, Collections.emptyMap(), tapInternalKey, alwaysIncludeWitnessUtxo);
|
||||||
psbtInputs.add(psbtInput);
|
psbtInputs.add(psbtInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +161,7 @@ public class PSBT {
|
||||||
for(int outputIndex = 0; outputIndex < outputNodes.size(); outputIndex++) {
|
for(int outputIndex = 0; outputIndex < outputNodes.size(); outputIndex++) {
|
||||||
WalletNode outputNode = outputNodes.get(outputIndex);
|
WalletNode outputNode = outputNodes.get(outputIndex);
|
||||||
if(outputNode == null) {
|
if(outputNode == null) {
|
||||||
PSBTOutput externalRecipientOutput = new PSBTOutput(null, null, null, Collections.emptyMap(), Collections.emptyMap(), null);
|
PSBTOutput externalRecipientOutput = new PSBTOutput(this, outputIndex, null, null, null, Collections.emptyMap(), Collections.emptyMap(), null);
|
||||||
psbtOutputs.add(externalRecipientOutput);
|
psbtOutputs.add(externalRecipientOutput);
|
||||||
} else {
|
} else {
|
||||||
TransactionOutput txOutput = transaction.getOutputs().get(outputIndex);
|
TransactionOutput txOutput = transaction.getOutputs().get(outputIndex);
|
||||||
|
@ -179,7 +192,7 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PSBTOutput walletOutput = new PSBTOutput(recipientWallet.getScriptType(), redeemScript, witnessScript, derivedPublicKeys, Collections.emptyMap(), tapInternalKey);
|
PSBTOutput walletOutput = new PSBTOutput(this, outputIndex, recipientWallet.getScriptType(), redeemScript, witnessScript, derivedPublicKeys, Collections.emptyMap(), tapInternalKey);
|
||||||
psbtOutputs.add(walletOutput);
|
psbtOutputs.add(walletOutput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,7 +203,12 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
public PSBT(byte[] psbt, boolean verifySignatures) throws PSBTParseException {
|
public PSBT(byte[] psbt, boolean verifySignatures) throws PSBTParseException {
|
||||||
|
this(psbt, verifySignatures, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PSBT(byte[] psbt, boolean verifySignatures, boolean verifyPrevTxids) throws PSBTParseException {
|
||||||
this.psbtBytes = psbt;
|
this.psbtBytes = psbt;
|
||||||
|
this.verifyPrevTxids = verifyPrevTxids;
|
||||||
parse(verifySignatures);
|
parse(verifySignatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +253,7 @@ public class PSBT {
|
||||||
seenInputs++;
|
seenInputs++;
|
||||||
if (seenInputs == inputs) {
|
if (seenInputs == inputs) {
|
||||||
currentState = STATE_OUTPUTS;
|
currentState = STATE_OUTPUTS;
|
||||||
parseInputEntries(inputEntryLists, verifySignatures);
|
parseInputEntries(inputEntryLists);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STATE_OUTPUTS:
|
case STATE_OUTPUTS:
|
||||||
|
@ -265,11 +283,15 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(currentState != STATE_END) {
|
if(currentState != STATE_END) {
|
||||||
if(transaction == null) {
|
if(getPsbtVersion() == 0 && transaction == null) {
|
||||||
throw new PSBTParseException("Missing transaction");
|
throw new PSBTParseException("Missing transaction");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(verifySignatures) {
|
||||||
|
verifySignatures(psbtInputs);
|
||||||
|
}
|
||||||
|
|
||||||
if(log.isDebugEnabled()) {
|
if(log.isDebugEnabled()) {
|
||||||
log.debug("Calculated fee at " + getFee());
|
log.debug("Calculated fee at " + getFee());
|
||||||
}
|
}
|
||||||
|
@ -282,7 +304,7 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
for(PSBTEntry entry : globalEntries) {
|
for(PSBTEntry entry : globalEntries) {
|
||||||
switch(entry.getKeyType()) {
|
switch((byte)entry.getKeyType()) {
|
||||||
case PSBT_GLOBAL_UNSIGNED_TX:
|
case PSBT_GLOBAL_UNSIGNED_TX:
|
||||||
entry.checkOneByteKey();
|
entry.checkOneByteKey();
|
||||||
Transaction transaction = new Transaction(entry.getData());
|
Transaction transaction = new Transaction(entry.getData());
|
||||||
|
@ -312,6 +334,40 @@ public class PSBT {
|
||||||
this.extendedPublicKeys.put(pubKey, keyDerivation);
|
this.extendedPublicKeys.put(pubKey, keyDerivation);
|
||||||
log.debug("Pubkey with master fingerprint " + keyDerivation.getMasterFingerprint() + " at path " + keyDerivation.getDerivationPath() + ": " + pubKey.getExtendedKey());
|
log.debug("Pubkey with master fingerprint " + keyDerivation.getMasterFingerprint() + " at path " + keyDerivation.getDerivationPath() + ": " + pubKey.getExtendedKey());
|
||||||
break;
|
break;
|
||||||
|
case PSBT_GLOBAL_TX_VERSION:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
long txVersion = Utils.readUint32(entry.getData(), 0);
|
||||||
|
this.txVersion = txVersion;
|
||||||
|
log.debug("PSBT tx version: " + txVersion);
|
||||||
|
break;
|
||||||
|
case PSBT_GLOBAL_FALLBACK_LOCKTIME:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
long fallbackLocktime = Utils.readUint32(entry.getData(), 0);
|
||||||
|
this.fallbackLocktime = fallbackLocktime;
|
||||||
|
log.debug("PSBT fallback locktime: " + fallbackLocktime);
|
||||||
|
break;
|
||||||
|
case PSBT_GLOBAL_INPUT_COUNT:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
VarInt varIntInputCount = new VarInt(entry.getData(), 0);
|
||||||
|
this.inputCount = varIntInputCount.value;
|
||||||
|
this.inputs = inputCount.intValue();
|
||||||
|
log.debug("PSBT input count: " + inputCount);
|
||||||
|
break;
|
||||||
|
case PSBT_GLOBAL_OUTPUT_COUNT:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
VarInt varIntOutputCount = new VarInt(entry.getData(), 0);
|
||||||
|
this.outputCount = varIntOutputCount.value;
|
||||||
|
this.outputs = outputCount.intValue();
|
||||||
|
log.debug("PSBT output count: " + outputCount);
|
||||||
|
break;
|
||||||
|
case PSBT_GLOBAL_TX_MODIFIABLE:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
if(entry.getData().length != 1) {
|
||||||
|
throw new PSBTParseException("Tx modifiable field was not a single byte");
|
||||||
|
}
|
||||||
|
this.modifiable = entry.getData()[0];
|
||||||
|
log.debug("PSBT tx modifiable: " + String.format("%8s", Integer.toBinaryString(modifiable & 0xFF)).replace(' ', '0'));
|
||||||
|
break;
|
||||||
case PSBT_GLOBAL_VERSION:
|
case PSBT_GLOBAL_VERSION:
|
||||||
entry.checkOneByteKey();
|
entry.checkOneByteKey();
|
||||||
int version = (int)Utils.readUint32(entry.getData(), 0);
|
int version = (int)Utils.readUint32(entry.getData(), 0);
|
||||||
|
@ -326,9 +382,45 @@ public class PSBT {
|
||||||
log.warn("PSBT global not recognized key type: " + entry.getKeyType());
|
log.warn("PSBT global not recognized key type: " + entry.getKeyType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(getPsbtVersion() == 0) {
|
||||||
|
if(transaction == null) {
|
||||||
|
throw new PSBTParseException("PSBT_GLOBAL_UNSIGNED_TX is required in PSBTv0");
|
||||||
|
}
|
||||||
|
if(txVersion != null) {
|
||||||
|
throw new PSBTParseException("PSBT_GLOBAL_TX_VERSION is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
if(fallbackLocktime != null) {
|
||||||
|
throw new PSBTParseException("PSBT_GLOBAL_FALLBACK_LOCKTIME is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
if(inputCount != null) {
|
||||||
|
throw new PSBTParseException("PSBT_GLOBAL_INPUT_COUNT is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
if(outputCount != null) {
|
||||||
|
throw new PSBTParseException("PSBT_GLOBAL_OUTPUT_COUNT is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
if(modifiable != null) {
|
||||||
|
throw new PSBTParseException("PSBT_GLOBAL_TX_MODIFIABLE is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
} else if(getPsbtVersion() == 1) {
|
||||||
|
throw new PSBTParseException("There is no PSBTv1");
|
||||||
|
} else if(getPsbtVersion() >= 2) {
|
||||||
|
if(transaction != null) {
|
||||||
|
throw new PSBTParseException("PSBT_GLOBAL_UNSIGNED_TX is not allowed in PSBTv2");
|
||||||
|
}
|
||||||
|
if(txVersion == null) {
|
||||||
|
throw new PSBTParseException("PSBT_GLOBAL_TX_VERSION is required in PSBTv2");
|
||||||
|
}
|
||||||
|
if(inputCount == null) {
|
||||||
|
throw new PSBTParseException("PSBT_GLOBAL_INPUT_COUNT is required in PSBTv2");
|
||||||
|
}
|
||||||
|
if(outputCount == null) {
|
||||||
|
throw new PSBTParseException("PSBT_GLOBAL_OUTPUT_COUNT is required in PSBTv2");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseInputEntries(List<List<PSBTEntry>> inputEntryLists, boolean verifySignatures) throws PSBTParseException {
|
private void parseInputEntries(List<List<PSBTEntry>> inputEntryLists) throws PSBTParseException {
|
||||||
for(List<PSBTEntry> inputEntries : inputEntryLists) {
|
for(List<PSBTEntry> inputEntries : inputEntryLists) {
|
||||||
PSBTEntry duplicate = findDuplicateKey(inputEntries);
|
PSBTEntry duplicate = findDuplicateKey(inputEntries);
|
||||||
if(duplicate != null) {
|
if(duplicate != null) {
|
||||||
|
@ -336,12 +428,34 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
int inputIndex = this.psbtInputs.size();
|
int inputIndex = this.psbtInputs.size();
|
||||||
PSBTInput input = new PSBTInput(this, inputEntries, transaction, inputIndex);
|
PSBTInput input = new PSBTInput(this, inputEntries, inputIndex);
|
||||||
this.psbtInputs.add(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(verifySignatures) {
|
if(getPsbtVersion() == 0) {
|
||||||
verifySignatures(psbtInputs);
|
if(input.prevTxid() != null) {
|
||||||
|
throw new PSBTParseException("PSBT_IN_PREV_TXID is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
if(input.prevIndex() != null) {
|
||||||
|
throw new PSBTParseException("PSBT_IN_OUTPUT_INDEX is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
if(input.sequence() != null) {
|
||||||
|
throw new PSBTParseException("PSBT_IN_SEQUENCE is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
if(input.getRequiredTimeLocktime() != null) {
|
||||||
|
throw new PSBTParseException("PSBT_IN_REQUIRED_TIME_LOCKTIME is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
if(input.getRequiredHeightLocktime() != null) {
|
||||||
|
throw new PSBTParseException("PSBT_IN_REQUIRED_HEIGHT_LOCKTIME is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
} else if(getPsbtVersion() >= 2) {
|
||||||
|
if(input.prevTxid() == null) {
|
||||||
|
throw new PSBTParseException("PSBT_IN_PREV_TXID is required in PSBTv2");
|
||||||
|
}
|
||||||
|
if(input.prevIndex() == null) {
|
||||||
|
throw new PSBTParseException("PSBT_IN_OUTPUT_INDEX is required in PSBTv2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.psbtInputs.add(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,11 +466,33 @@ public class PSBT {
|
||||||
throw new PSBTParseException("Found duplicate key for PSBT output: " + Utils.bytesToHex(duplicate.getKey()));
|
throw new PSBTParseException("Found duplicate key for PSBT output: " + Utils.bytesToHex(duplicate.getKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
PSBTOutput output = new PSBTOutput(outputEntries);
|
int outputIndex = this.psbtOutputs.size();
|
||||||
|
PSBTOutput output = new PSBTOutput(this, outputEntries, outputIndex);
|
||||||
|
|
||||||
|
if(getPsbtVersion() == 0) {
|
||||||
|
if(output.amount() != null) {
|
||||||
|
throw new PSBTParseException("PSBT_OUT_AMOUNT is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
if(output.script() != null) {
|
||||||
|
throw new PSBTParseException("PSBT_OUT_SCRIPT is not allowed in PSBTv0");
|
||||||
|
}
|
||||||
|
} else if(getPsbtVersion() >= 2) {
|
||||||
|
if(output.amount() == null) {
|
||||||
|
throw new PSBTParseException("PSBT_OUT_AMOUNT is required in PSBTv2");
|
||||||
|
}
|
||||||
|
if(output.script() == null) {
|
||||||
|
throw new PSBTParseException("PSBT_OUT_SCRIPT is required in PSBTv2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.psbtOutputs.add(output);
|
this.psbtOutputs.add(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getPsbtVersion() {
|
||||||
|
return version == null ? 0 : version;
|
||||||
|
}
|
||||||
|
|
||||||
private PSBTEntry findDuplicateKey(List<PSBTEntry> entries) {
|
private PSBTEntry findDuplicateKey(List<PSBTEntry> entries) {
|
||||||
Set<String> checkSet = new HashSet<>();
|
Set<String> checkSet = new HashSet<>();
|
||||||
for(PSBTEntry entry: entries) {
|
for(PSBTEntry entry: entries) {
|
||||||
|
@ -382,9 +518,8 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < transaction.getOutputs().size(); i++) {
|
for(PSBTOutput output : psbtOutputs) {
|
||||||
TransactionOutput output = transaction.getOutputs().get(i);
|
fee -= output.getAmount();
|
||||||
fee -= output.getValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fee;
|
return fee;
|
||||||
|
@ -397,7 +532,7 @@ public class PSBT {
|
||||||
private void verifySignatures(List<PSBTInput> psbtInputs) throws PSBTSignatureException {
|
private void verifySignatures(List<PSBTInput> psbtInputs) throws PSBTSignatureException {
|
||||||
for(PSBTInput input : psbtInputs) {
|
for(PSBTInput input : psbtInputs) {
|
||||||
boolean verified = input.verifySignatures();
|
boolean verified = input.verifySignatures();
|
||||||
if(!verified && input.getPartialSignatures().size() > 0) {
|
if(!verified && !input.getPartialSignatures().isEmpty()) {
|
||||||
throw new PSBTSignatureException("Unverifiable partial signatures provided");
|
throw new PSBTSignatureException("Unverifiable partial signatures provided");
|
||||||
}
|
}
|
||||||
if(!verified && input.isTaproot() && input.getTapKeyPathSignature() != null) {
|
if(!verified && input.isTaproot() && input.getTapKeyPathSignature() != null) {
|
||||||
|
@ -439,7 +574,7 @@ public class PSBT {
|
||||||
private List<PSBTEntry> getGlobalEntries() {
|
private List<PSBTEntry> getGlobalEntries() {
|
||||||
List<PSBTEntry> entries = new ArrayList<>();
|
List<PSBTEntry> entries = new ArrayList<>();
|
||||||
|
|
||||||
if(transaction != null) {
|
if(getPsbtVersion() == 0 && transaction != null) {
|
||||||
entries.add(populateEntry(PSBT_GLOBAL_UNSIGNED_TX, null, transaction.bitcoinSerialize(false)));
|
entries.add(populateEntry(PSBT_GLOBAL_UNSIGNED_TX, null, transaction.bitcoinSerialize(false)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,6 +582,30 @@ public class PSBT {
|
||||||
entries.add(populateEntry(PSBT_GLOBAL_BIP32_PUBKEY, entry.getKey().getExtendedKeyBytes(), serializeKeyDerivation(entry.getValue())));
|
entries.add(populateEntry(PSBT_GLOBAL_BIP32_PUBKEY, entry.getKey().getExtendedKeyBytes(), serializeKeyDerivation(entry.getValue())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(getPsbtVersion() >= 2) {
|
||||||
|
if(txVersion != null) {
|
||||||
|
byte[] txVersionBytes = new byte[4];
|
||||||
|
Utils.uint32ToByteArrayLE(txVersion, txVersionBytes, 0);
|
||||||
|
entries.add(populateEntry(PSBT_GLOBAL_TX_VERSION, null, txVersionBytes));
|
||||||
|
}
|
||||||
|
if(fallbackLocktime != null) {
|
||||||
|
byte[] fallbackLocktimeBytes = new byte[4];
|
||||||
|
Utils.uint32ToByteArrayLE(fallbackLocktime, fallbackLocktimeBytes, 0);
|
||||||
|
entries.add(populateEntry(PSBT_GLOBAL_FALLBACK_LOCKTIME, null, fallbackLocktimeBytes));
|
||||||
|
}
|
||||||
|
if(inputCount != null) {
|
||||||
|
VarInt varIntInputCount = new VarInt(inputCount);
|
||||||
|
entries.add(populateEntry(PSBT_GLOBAL_INPUT_COUNT, null, varIntInputCount.encode()));
|
||||||
|
}
|
||||||
|
if(outputCount != null) {
|
||||||
|
VarInt varIntOutputCount = new VarInt(outputCount);
|
||||||
|
entries.add(populateEntry(PSBT_GLOBAL_OUTPUT_COUNT, null, varIntOutputCount.encode()));
|
||||||
|
}
|
||||||
|
if(modifiable != null) {
|
||||||
|
entries.add(populateEntry(PSBT_GLOBAL_TX_MODIFIABLE, null, new byte[] { modifiable }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(version != null) {
|
if(version != null) {
|
||||||
byte[] versionBytes = new byte[4];
|
byte[] versionBytes = new byte[4];
|
||||||
Utils.uint32ToByteArrayLE(version, versionBytes, 0);
|
Utils.uint32ToByteArrayLE(version, versionBytes, 0);
|
||||||
|
@ -479,7 +638,7 @@ public class PSBT {
|
||||||
baos.writeBytes(new byte[] {(byte)0x00});
|
baos.writeBytes(new byte[] {(byte)0x00});
|
||||||
|
|
||||||
for(PSBTInput psbtInput : getPsbtInputs()) {
|
for(PSBTInput psbtInput : getPsbtInputs()) {
|
||||||
List<PSBTEntry> inputEntries = psbtInput.getInputEntries();
|
List<PSBTEntry> inputEntries = psbtInput.getInputEntries(getPsbtVersion());
|
||||||
for(PSBTEntry entry : inputEntries) {
|
for(PSBTEntry entry : inputEntries) {
|
||||||
if((includeXpubs || (entry.getKeyType() != PSBT_IN_BIP32_DERIVATION && entry.getKeyType() != PSBT_IN_PROPRIETARY
|
if((includeXpubs || (entry.getKeyType() != PSBT_IN_BIP32_DERIVATION && entry.getKeyType() != PSBT_IN_PROPRIETARY
|
||||||
&& entry.getKeyType() != PSBT_IN_TAP_INTERNAL_KEY && entry.getKeyType() != PSBT_IN_TAP_BIP32_DERIVATION))
|
&& entry.getKeyType() != PSBT_IN_TAP_INTERNAL_KEY && entry.getKeyType() != PSBT_IN_TAP_BIP32_DERIVATION))
|
||||||
|
@ -491,7 +650,7 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
for(PSBTOutput psbtOutput : getPsbtOutputs()) {
|
for(PSBTOutput psbtOutput : getPsbtOutputs()) {
|
||||||
List<PSBTEntry> outputEntries = psbtOutput.getOutputEntries();
|
List<PSBTEntry> outputEntries = psbtOutput.getOutputEntries(getPsbtVersion());
|
||||||
for(PSBTEntry entry : outputEntries) {
|
for(PSBTEntry entry : outputEntries) {
|
||||||
if(includeXpubs || (entry.getKeyType() != PSBT_OUT_REDEEM_SCRIPT && entry.getKeyType() != PSBT_OUT_WITNESS_SCRIPT
|
if(includeXpubs || (entry.getKeyType() != PSBT_OUT_REDEEM_SCRIPT && entry.getKeyType() != PSBT_OUT_WITNESS_SCRIPT
|
||||||
&& entry.getKeyType() != PSBT_OUT_BIP32_DERIVATION && entry.getKeyType() != PSBT_OUT_PROPRIETARY
|
&& entry.getKeyType() != PSBT_OUT_BIP32_DERIVATION && entry.getKeyType() != PSBT_OUT_PROPRIETARY
|
||||||
|
@ -512,7 +671,11 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void combine(PSBT psbt) {
|
public void combine(PSBT psbt) {
|
||||||
byte[] txBytes = transaction.bitcoinSerialize();
|
if(getPsbtVersion() != psbt.getPsbtVersion()) {
|
||||||
|
psbt.convertVersion(getPsbtVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] txBytes = getTransaction().bitcoinSerialize();
|
||||||
byte[] psbtTxBytes = psbt.getTransaction().bitcoinSerialize();
|
byte[] psbtTxBytes = psbt.getTransaction().bitcoinSerialize();
|
||||||
|
|
||||||
if(!Arrays.equals(txBytes, psbtTxBytes)) {
|
if(!Arrays.equals(txBytes, psbtTxBytes)) {
|
||||||
|
@ -551,7 +714,7 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction finalTransaction = new Transaction(transaction.bitcoinSerialize());
|
Transaction finalTransaction = new Transaction(getTransaction().bitcoinSerialize());
|
||||||
|
|
||||||
if(hasWitness && !finalTransaction.isSegwit()) {
|
if(hasWitness && !finalTransaction.isSegwit()) {
|
||||||
finalTransaction.setSegwitFlag(Transaction.DEFAULT_SEGWIT_FLAG);
|
finalTransaction.setSegwitFlag(Transaction.DEFAULT_SEGWIT_FLAG);
|
||||||
|
@ -596,7 +759,7 @@ public class PSBT {
|
||||||
|
|
||||||
public void moveInput(int fromIndex, int toIndex) {
|
public void moveInput(int fromIndex, int toIndex) {
|
||||||
moveItem(psbtInputs, fromIndex, toIndex);
|
moveItem(psbtInputs, fromIndex, toIndex);
|
||||||
transaction.moveInput(fromIndex, toIndex);
|
getTransaction().moveInput(fromIndex, toIndex);
|
||||||
for(int i = 0; i < psbtInputs.size(); i++) {
|
for(int i = 0; i < psbtInputs.size(); i++) {
|
||||||
psbtInputs.get(i).setIndex(i);
|
psbtInputs.get(i).setIndex(i);
|
||||||
}
|
}
|
||||||
|
@ -604,7 +767,10 @@ public class PSBT {
|
||||||
|
|
||||||
public void moveOutput(int fromIndex, int toIndex) {
|
public void moveOutput(int fromIndex, int toIndex) {
|
||||||
moveItem(psbtOutputs, fromIndex, toIndex);
|
moveItem(psbtOutputs, fromIndex, toIndex);
|
||||||
transaction.moveOutput(fromIndex, toIndex);
|
getTransaction().moveOutput(fromIndex, toIndex);
|
||||||
|
for(int i = 0; i < psbtOutputs.size(); i++) {
|
||||||
|
psbtOutputs.get(i).setIndex(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> void moveItem(List<T> list, int fromIndex, int toIndex) {
|
private <T> void moveItem(List<T> list, int fromIndex, int toIndex) {
|
||||||
|
@ -625,6 +791,33 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Transaction getTransaction() {
|
public Transaction getTransaction() {
|
||||||
|
return getTransaction(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transaction getTransaction(boolean setSequence) {
|
||||||
|
if(getPsbtVersion() >= 2) {
|
||||||
|
Transaction transaction = new Transaction();
|
||||||
|
transaction.setVersion(txVersion);
|
||||||
|
transaction.setLocktime(getLocktime(psbtInputs, fallbackLocktime));
|
||||||
|
for(PSBTInput psbtInput : getPsbtInputs()) {
|
||||||
|
TransactionInput transactionInput = transaction.addInput(psbtInput.getPrevTxid(), psbtInput.getPrevIndex(), new Script(new byte[0]));
|
||||||
|
if(setSequence) {
|
||||||
|
if(psbtInput.getSequence() == null) {
|
||||||
|
transactionInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED);
|
||||||
|
} else {
|
||||||
|
transactionInput.setSequenceNumber(psbtInput.getSequence());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Sequence number is set to zero to provide a static txid while updating
|
||||||
|
transactionInput.setSequenceNumber(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(PSBTOutput psbtOutput : getPsbtOutputs()) {
|
||||||
|
transaction.addOutput(psbtOutput.getAmount(), psbtOutput.getScript());
|
||||||
|
}
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,6 +833,72 @@ public class PSBT {
|
||||||
return extendedPublicKeys;
|
return extendedPublicKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getTxVersion() {
|
||||||
|
if(getPsbtVersion() >= 2) {
|
||||||
|
return txVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getTransaction().getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getFallbackLocktime() {
|
||||||
|
if(getPsbtVersion() >= 2) {
|
||||||
|
return fallbackLocktime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getTransaction().getLocktime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getInputCount() {
|
||||||
|
if(getPsbtVersion() >= 2) {
|
||||||
|
return inputCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (long)getTransaction().getInputs().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getOutputCount() {
|
||||||
|
if(getPsbtVersion() >= 2) {
|
||||||
|
return outputCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (long)getTransaction().getOutputs().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Byte getModifiable() {
|
||||||
|
return modifiable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isInputsModifiable() {
|
||||||
|
return modifiable == null ? null : (modifiable & (byte)0x01) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputsModifiable(boolean inputsModifiable) {
|
||||||
|
if(modifiable != null) {
|
||||||
|
modifiable = inputsModifiable ? (byte)(modifiable | (byte)0x01) : (byte)(modifiable & (byte)0xFE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isOutputsModifiable() {
|
||||||
|
return modifiable == null ? null : (modifiable & (byte)0x02) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputsModifiable(boolean outputsModifiable) {
|
||||||
|
if(modifiable != null) {
|
||||||
|
modifiable = outputsModifiable ? (byte)(modifiable | (byte)0x02) : (byte)(modifiable & (byte)0xFD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isSigHashSingleSignaturePresent() {
|
||||||
|
return modifiable == null ? null : (modifiable & (byte)0x04) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSigHashSingleSignaturePresent(boolean sigHashSingleSignaturePresent) {
|
||||||
|
if(modifiable != null) {
|
||||||
|
modifiable = sigHashSingleSignaturePresent ? (byte)(modifiable | (byte)0x04) : (byte)(modifiable & (byte)0xFB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getGlobalProprietary() {
|
public Map<String, String> getGlobalProprietary() {
|
||||||
return globalProprietary;
|
return globalProprietary;
|
||||||
}
|
}
|
||||||
|
@ -656,6 +915,93 @@ public class PSBT {
|
||||||
return Base64.toBase64String(serialize(includeXpubs, true));
|
return Base64.toBase64String(serialize(includeXpubs, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void convertVersion(int version) {
|
||||||
|
if(version < 0) {
|
||||||
|
throw new IllegalArgumentException("Version must be zero or positive");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert from PSBTv2+ to PSBTv0
|
||||||
|
if(getPsbtVersion() >= 2 && version == 0) {
|
||||||
|
this.transaction = getTransaction();
|
||||||
|
this.txVersion = null;
|
||||||
|
this.fallbackLocktime = null;
|
||||||
|
this.inputCount = null;
|
||||||
|
this.outputCount = null;
|
||||||
|
this.modifiable = null;
|
||||||
|
|
||||||
|
for(PSBTInput psbtInput : getPsbtInputs()) {
|
||||||
|
psbtInput.setPrevTxid(null);
|
||||||
|
psbtInput.setPrevIndex(null);
|
||||||
|
psbtInput.setSequence(null);
|
||||||
|
psbtInput.setRequiredTimeLocktime(null);
|
||||||
|
psbtInput.setRequiredHeightLocktime(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(PSBTOutput psbtOutput : getPsbtOutputs()) {
|
||||||
|
psbtOutput.setAmount(null);
|
||||||
|
psbtOutput.setScript(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert from PSBTv0 to PSBTv2+
|
||||||
|
if(getPsbtVersion() == 0 && version >= 2) {
|
||||||
|
this.txVersion = transaction.getVersion();
|
||||||
|
this.fallbackLocktime = transaction.getLocktime();
|
||||||
|
this.inputCount = (long)transaction.getInputs().size();
|
||||||
|
this.outputCount = (long)transaction.getOutputs().size();
|
||||||
|
this.modifiable = null;
|
||||||
|
|
||||||
|
for(PSBTInput psbtInput : getPsbtInputs()) {
|
||||||
|
psbtInput.setPrevTxid(psbtInput.getPrevTxid());
|
||||||
|
psbtInput.setPrevIndex(psbtInput.getPrevIndex());
|
||||||
|
psbtInput.setSequence(psbtInput.getSequence());
|
||||||
|
psbtInput.setRequiredTimeLocktime(null);
|
||||||
|
psbtInput.setRequiredHeightLocktime(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(PSBTOutput psbtOutput : getPsbtOutputs()) {
|
||||||
|
psbtOutput.setAmount(psbtOutput.getAmount());
|
||||||
|
psbtOutput.setScript(psbtOutput.getScript());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.transaction = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getLocktime(List<PSBTInput> psbtInputs, Long fallbackLocktime) {
|
||||||
|
long fallback = (fallbackLocktime != null) ? fallbackLocktime : 0L;
|
||||||
|
|
||||||
|
OptionalLong maxHeightLocktime = psbtInputs.stream().map(PSBTInput::getRequiredHeightLocktime).filter(Objects::nonNull).mapToLong(Long::longValue).max();
|
||||||
|
OptionalLong maxTimeLocktime = psbtInputs.stream().map(PSBTInput::getRequiredTimeLocktime).filter(Objects::nonNull).mapToLong(Long::longValue).max();
|
||||||
|
|
||||||
|
boolean allHeight = psbtInputs.stream().map(PSBTInput::getRequiredHeightLocktime).allMatch(Objects::nonNull);
|
||||||
|
boolean allTime = psbtInputs.stream().map(PSBTInput::getRequiredTimeLocktime).allMatch(Objects::nonNull);
|
||||||
|
|
||||||
|
if(maxHeightLocktime.isEmpty() && maxTimeLocktime.isEmpty()) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(maxHeightLocktime.isPresent() && allHeight) {
|
||||||
|
return maxHeightLocktime.getAsLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(maxTimeLocktime.isPresent() && allTime) {
|
||||||
|
return maxTimeLocktime.getAsLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(maxHeightLocktime.isPresent() && maxTimeLocktime.isPresent()) {
|
||||||
|
return maxHeightLocktime.getAsLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxHeightLocktime.orElse(maxTimeLocktime.orElse(fallback));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isVerifyPrevTxids() {
|
||||||
|
return verifyPrevTxids;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isPSBT(byte[] b) {
|
public static boolean isPSBT(byte[] b) {
|
||||||
try {
|
try {
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(b);
|
ByteBuffer buffer = ByteBuffer.wrap(b);
|
||||||
|
@ -687,6 +1033,10 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PSBT fromString(String strPSBT, boolean verifySignatures) throws PSBTParseException {
|
public static PSBT fromString(String strPSBT, boolean verifySignatures) throws PSBTParseException {
|
||||||
|
return fromString(strPSBT, verifySignatures, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PSBT fromString(String strPSBT, boolean verifySignatures, boolean verifyPrevTxids) throws PSBTParseException {
|
||||||
if (!isPSBT(strPSBT)) {
|
if (!isPSBT(strPSBT)) {
|
||||||
throw new PSBTParseException("Provided string is not a PSBT");
|
throw new PSBTParseException("Provided string is not a PSBT");
|
||||||
}
|
}
|
||||||
|
@ -696,6 +1046,6 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] psbtBytes = Utils.hexToBytes(strPSBT);
|
byte[] psbtBytes = Utils.hexToBytes(strPSBT);
|
||||||
return new PSBT(psbtBytes, verifySignatures);
|
return new PSBT(psbtBytes, verifySignatures, verifyPrevTxids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class PSBTEntry {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
PSBTEntry(ByteBuffer psbtByteBuffer) throws PSBTParseException {
|
public PSBTEntry(ByteBuffer psbtByteBuffer) throws PSBTParseException {
|
||||||
int keyLen = readCompactInt(psbtByteBuffer);
|
int keyLen = readCompactInt(psbtByteBuffer);
|
||||||
|
|
||||||
if (keyLen == 0x00) {
|
if (keyLen == 0x00) {
|
||||||
|
@ -259,4 +259,16 @@ public class PSBTEntry {
|
||||||
throw new PSBTParseException("PSBT key type must be one byte plus x only pub key");
|
throw new PSBTParseException("PSBT key type must be one byte plus x only pub key");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void checkOneBytePlusRipe160Key() throws PSBTParseException {
|
||||||
|
if(this.getKey().length != 21) {
|
||||||
|
throw new PSBTParseException("PSBT key type must be one byte plus Ripe160MD hash");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkOneBytePlusSha256Key() throws PSBTParseException {
|
||||||
|
if(this.getKey().length != 33) {
|
||||||
|
throw new PSBTParseException("PSBT key type must be one byte plus SHA256 hash");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,15 @@ public class PSBTInput {
|
||||||
public static final byte PSBT_IN_FINAL_SCRIPTSIG = 0x07;
|
public static final byte PSBT_IN_FINAL_SCRIPTSIG = 0x07;
|
||||||
public static final byte PSBT_IN_FINAL_SCRIPTWITNESS = 0x08;
|
public static final byte PSBT_IN_FINAL_SCRIPTWITNESS = 0x08;
|
||||||
public static final byte PSBT_IN_POR_COMMITMENT = 0x09;
|
public static final byte PSBT_IN_POR_COMMITMENT = 0x09;
|
||||||
|
public static final byte PSBT_IN_RIPEMD160 = 0x0a;
|
||||||
|
public static final byte PSBT_IN_SHA256 = 0x0b;
|
||||||
|
public static final byte PSBT_IN_HASH160 = 0x0c;
|
||||||
|
public static final byte PSBT_IN_HASH256 = 0x0d;
|
||||||
|
public static final byte PSBT_IN_PREVIOUS_TXID = 0x0e;
|
||||||
|
public static final byte PSBT_IN_OUTPUT_INDEX = 0x0f;
|
||||||
|
public static final byte PSBT_IN_SEQUENCE = 0x10;
|
||||||
|
public static final byte PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11;
|
||||||
|
public static final byte PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12;
|
||||||
public static final byte PSBT_IN_PROPRIETARY = (byte)0xfc;
|
public static final byte PSBT_IN_PROPRIETARY = (byte)0xfc;
|
||||||
public static final byte PSBT_IN_TAP_KEY_SIG = 0x13;
|
public static final byte PSBT_IN_TAP_KEY_SIG = 0x13;
|
||||||
public static final byte PSBT_IN_TAP_BIP32_DERIVATION = 0x16;
|
public static final byte PSBT_IN_TAP_BIP32_DERIVATION = 0x16;
|
||||||
|
@ -42,24 +51,33 @@ public class PSBTInput {
|
||||||
private Script finalScriptSig;
|
private Script finalScriptSig;
|
||||||
private TransactionWitness finalScriptWitness;
|
private TransactionWitness finalScriptWitness;
|
||||||
private String porCommitment;
|
private String porCommitment;
|
||||||
|
private byte[] ripeMd160Preimage;
|
||||||
|
private byte[] sha256Preimage;
|
||||||
|
private byte[] hash160Preimage;
|
||||||
|
private byte[] hash256Preimage;
|
||||||
private final Map<String, String> proprietary = new LinkedHashMap<>();
|
private final Map<String, String> proprietary = new LinkedHashMap<>();
|
||||||
private TransactionSignature tapKeyPathSignature;
|
private TransactionSignature tapKeyPathSignature;
|
||||||
private Map<ECKey, Map<KeyDerivation, List<Sha256Hash>>> tapDerivedPublicKeys = new LinkedHashMap<>();
|
private Map<ECKey, Map<KeyDerivation, List<Sha256Hash>>> tapDerivedPublicKeys = new LinkedHashMap<>();
|
||||||
private ECKey tapInternalKey;
|
private ECKey tapInternalKey;
|
||||||
|
|
||||||
private final Transaction transaction;
|
//PSBTv2 fields
|
||||||
|
private Sha256Hash prevTxid;
|
||||||
|
private Long prevIndex;
|
||||||
|
private Long sequence;
|
||||||
|
private Long requiredTimeLocktime;
|
||||||
|
private Long requiredHeightLocktime;
|
||||||
|
|
||||||
private int index;
|
private int index;
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(PSBTInput.class);
|
private static final Logger log = LoggerFactory.getLogger(PSBTInput.class);
|
||||||
|
|
||||||
PSBTInput(PSBT psbt, Transaction transaction, int index) {
|
PSBTInput(PSBT psbt, int index) {
|
||||||
this.psbt = psbt;
|
this.psbt = psbt;
|
||||||
this.transaction = transaction;
|
|
||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
PSBTInput(PSBT psbt, ScriptType scriptType, Transaction transaction, int index, Transaction utxo, int utxoIndex, Script redeemScript, Script witnessScript, Map<ECKey, KeyDerivation> derivedPublicKeys, Map<String, String> proprietary, ECKey tapInternalKey, boolean alwaysAddNonWitnessTx) {
|
PSBTInput(PSBT psbt, ScriptType scriptType, int index, Transaction utxo, int utxoIndex, Script redeemScript, Script witnessScript, Map<ECKey, KeyDerivation> derivedPublicKeys, Map<String, String> proprietary, ECKey tapInternalKey, boolean alwaysAddNonWitnessTx) {
|
||||||
this(psbt, transaction, index);
|
this(psbt, index);
|
||||||
|
|
||||||
if(Arrays.asList(ScriptType.WITNESS_TYPES).contains(scriptType)) {
|
if(Arrays.asList(ScriptType.WITNESS_TYPES).contains(scriptType)) {
|
||||||
this.witnessUtxo = utxo.getOutputs().get(utxoIndex);
|
this.witnessUtxo = utxo.getOutputs().get(utxoIndex);
|
||||||
|
@ -91,18 +109,30 @@ public class PSBTInput {
|
||||||
this.sigHash = getDefaultSigHash();
|
this.sigHash = getDefaultSigHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
PSBTInput(PSBT psbt, List<PSBTEntry> inputEntries, Transaction transaction, int index) throws PSBTParseException {
|
PSBTInput(PSBT psbt, List<PSBTEntry> inputEntries, int index) throws PSBTParseException {
|
||||||
this.psbt = psbt;
|
this(psbt, index);
|
||||||
for(PSBTEntry entry : inputEntries) {
|
List<PSBTEntry> sortedEntries = new ArrayList<>(inputEntries);
|
||||||
switch(entry.getKeyType()) {
|
sortedEntries.sort((o1, o2) -> {
|
||||||
|
int found1 = o1.getKeyType() == PSBT_IN_PREVIOUS_TXID || o1.getKeyType() == PSBT_IN_OUTPUT_INDEX ? 1 : 0;
|
||||||
|
int found2 = o2.getKeyType() == PSBT_IN_PREVIOUS_TXID || o2.getKeyType() == PSBT_IN_OUTPUT_INDEX ? 1 : 0;
|
||||||
|
return found2 - found1;
|
||||||
|
});
|
||||||
|
|
||||||
|
for(PSBTEntry entry : sortedEntries) {
|
||||||
|
switch((byte)entry.getKeyType()) {
|
||||||
case PSBT_IN_NON_WITNESS_UTXO:
|
case PSBT_IN_NON_WITNESS_UTXO:
|
||||||
entry.checkOneByteKey();
|
entry.checkOneByteKey();
|
||||||
Transaction nonWitnessTx = new Transaction(entry.getData());
|
Transaction nonWitnessTx = new Transaction(entry.getData());
|
||||||
nonWitnessTx.verify();
|
nonWitnessTx.verify();
|
||||||
Sha256Hash inputHash = nonWitnessTx.calculateTxId(false);
|
if(psbt.isVerifyPrevTxids()) {
|
||||||
Sha256Hash outpointHash = transaction.getInputs().get(index).getOutpoint().getHash();
|
Sha256Hash inputHash = nonWitnessTx.calculateTxId(false);
|
||||||
if(!outpointHash.equals(inputHash)) {
|
Sha256Hash outpointHash = getPrevTxid();
|
||||||
throw new PSBTParseException("Hash of provided non witness utxo transaction " + inputHash + " does not match transaction input outpoint hash " + outpointHash + " at index " + index);
|
if(outpointHash == null) {
|
||||||
|
throw new PSBTParseException("Outpoint hash not present for input " + index);
|
||||||
|
}
|
||||||
|
if(!outpointHash.equals(inputHash)) {
|
||||||
|
throw new PSBTParseException("Hash of provided non witness utxo transaction " + inputHash + " does not match transaction input outpoint hash " + outpointHash + " at index " + index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.nonWitnessUtxo = nonWitnessTx;
|
this.nonWitnessUtxo = nonWitnessTx;
|
||||||
|
@ -151,7 +181,11 @@ public class PSBTInput {
|
||||||
Script redeemScript = new Script(entry.getData());
|
Script redeemScript = new Script(entry.getData());
|
||||||
Script scriptPubKey = null;
|
Script scriptPubKey = null;
|
||||||
if(this.nonWitnessUtxo != null) {
|
if(this.nonWitnessUtxo != null) {
|
||||||
scriptPubKey = this.nonWitnessUtxo.getOutputs().get((int)transaction.getInputs().get(index).getOutpoint().getIndex()).getScript();
|
Long prevIndex = getPrevIndex();
|
||||||
|
if(prevIndex == null) {
|
||||||
|
throw new PSBTParseException("Outpoint index not present for input " + index);
|
||||||
|
}
|
||||||
|
scriptPubKey = this.nonWitnessUtxo.getOutputs().get(prevIndex.intValue()).getScript();
|
||||||
} else if(this.witnessUtxo != null) {
|
} else if(this.witnessUtxo != null) {
|
||||||
scriptPubKey = this.witnessUtxo.getScript();
|
scriptPubKey = this.witnessUtxo.getScript();
|
||||||
if(!P2WPKH.isScriptType(redeemScript) && !P2WSH.isScriptType(redeemScript)) { //Witness UTXO should only be provided for P2SH-P2WPKH or P2SH-P2WSH
|
if(!P2WPKH.isScriptType(redeemScript) && !P2WSH.isScriptType(redeemScript)) { //Witness UTXO should only be provided for P2SH-P2WPKH or P2SH-P2WSH
|
||||||
|
@ -214,6 +248,71 @@ public class PSBTInput {
|
||||||
this.porCommitment = porMessage;
|
this.porCommitment = porMessage;
|
||||||
log.debug("Found input POR commitment message " + porMessage);
|
log.debug("Found input POR commitment message " + porMessage);
|
||||||
break;
|
break;
|
||||||
|
case PSBT_IN_RIPEMD160:
|
||||||
|
entry.checkOneBytePlusRipe160Key();
|
||||||
|
if(!Arrays.equals(entry.getKeyData(), Ripemd160.getHash(entry.getData()))) {
|
||||||
|
throw new PSBTParseException("Hash of PSBT_IN_RIPEMD160 preimage did not match provided hash " + Utils.bytesToHex(entry.getKeyData()) + " " + Utils.bytesToHex(entry.getData()));
|
||||||
|
}
|
||||||
|
this.ripeMd160Preimage = entry.getData();
|
||||||
|
log.debug("Found input RIPEMD160 preimage " + Utils.bytesToHex(entry.getData()));
|
||||||
|
break;
|
||||||
|
case PSBT_IN_SHA256:
|
||||||
|
entry.checkOneBytePlusSha256Key();
|
||||||
|
if(!Arrays.equals(entry.getKeyData(), Sha256Hash.hash(entry.getData()))) {
|
||||||
|
throw new PSBTParseException("Hash of PSBT_IN_SHA256 preimage did not match provided hash " + Utils.bytesToHex(entry.getKeyData()) + " " + Utils.bytesToHex(entry.getData()));
|
||||||
|
}
|
||||||
|
this.sha256Preimage = entry.getData();
|
||||||
|
log.debug("Found input SHA256 preimage " + Utils.bytesToHex(entry.getData()));
|
||||||
|
break;
|
||||||
|
case PSBT_IN_HASH160:
|
||||||
|
entry.checkOneBytePlusRipe160Key();
|
||||||
|
if(!Arrays.equals(entry.getKeyData(), Utils.sha256hash160(entry.getData()))) {
|
||||||
|
throw new PSBTParseException("Hash of PSBT_IN_HASH160 preimage did not match provided hash " + Utils.bytesToHex(entry.getKeyData()) + " " + Utils.bytesToHex(entry.getData()));
|
||||||
|
}
|
||||||
|
this.hash160Preimage = entry.getData();
|
||||||
|
log.debug("Found input HASH160 preimage " + Utils.bytesToHex(entry.getData()));
|
||||||
|
break;
|
||||||
|
case PSBT_IN_HASH256:
|
||||||
|
entry.checkOneBytePlusSha256Key();
|
||||||
|
if(!Arrays.equals(entry.getKeyData(), Sha256Hash.hashTwice(entry.getData()))) {
|
||||||
|
throw new PSBTParseException("Hash of PSBT_IN_HASH256 preimage did not match provided hash " + Utils.bytesToHex(entry.getKeyData()) + " " + Utils.bytesToHex(entry.getData()));
|
||||||
|
}
|
||||||
|
this.hash256Preimage = entry.getData();
|
||||||
|
log.debug("Found input HASH256 preimage " + Utils.bytesToHex(entry.getData()));
|
||||||
|
break;
|
||||||
|
case PSBT_IN_PREVIOUS_TXID:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
this.prevTxid = Sha256Hash.wrap(entry.getData());
|
||||||
|
log.debug("Found input previous txid " + Utils.bytesToHex(entry.getData()));
|
||||||
|
break;
|
||||||
|
case PSBT_IN_OUTPUT_INDEX:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
this.prevIndex = Utils.readUint32(entry.getData(), 0);
|
||||||
|
log.debug("Found input previous output index " + this.prevIndex);
|
||||||
|
break;
|
||||||
|
case PSBT_IN_SEQUENCE:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
this.sequence = Utils.readUint32(entry.getData(), 0);
|
||||||
|
log.debug("Found input sequence " + this.sequence);
|
||||||
|
break;
|
||||||
|
case PSBT_IN_REQUIRED_TIME_LOCKTIME:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
long requiredTimeLocktime = Utils.readUint32(entry.getData(), 0);
|
||||||
|
if(requiredTimeLocktime < 500000000) {
|
||||||
|
throw new PSBTParseException("Required time locktime is less than 500000000");
|
||||||
|
}
|
||||||
|
this.requiredTimeLocktime = requiredTimeLocktime;
|
||||||
|
log.debug("Found input required time locktime " + this.requiredTimeLocktime);
|
||||||
|
break;
|
||||||
|
case PSBT_IN_REQUIRED_HEIGHT_LOCKTIME:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
long requiredHeightLocktime = Utils.readUint32(entry.getData(), 0);
|
||||||
|
if(requiredHeightLocktime >= 500000000) {
|
||||||
|
throw new PSBTParseException("Required time locktime is greater than or equal to 500000000");
|
||||||
|
}
|
||||||
|
this.requiredHeightLocktime = requiredHeightLocktime;
|
||||||
|
log.debug("Found input required height locktime " + this.requiredHeightLocktime);
|
||||||
|
break;
|
||||||
case PSBT_IN_PROPRIETARY:
|
case PSBT_IN_PROPRIETARY:
|
||||||
this.proprietary.put(Utils.bytesToHex(entry.getKeyData()), Utils.bytesToHex(entry.getData()));
|
this.proprietary.put(Utils.bytesToHex(entry.getKeyData()), Utils.bytesToHex(entry.getData()));
|
||||||
log.debug("Found proprietary input " + Utils.bytesToHex(entry.getKeyData()) + ": " + Utils.bytesToHex(entry.getData()));
|
log.debug("Found proprietary input " + Utils.bytesToHex(entry.getKeyData()) + ": " + Utils.bytesToHex(entry.getData()));
|
||||||
|
@ -245,12 +344,9 @@ public class PSBTInput {
|
||||||
log.warn("PSBT input not recognized key type: " + entry.getKeyType());
|
log.warn("PSBT input not recognized key type: " + entry.getKeyType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.transaction = transaction;
|
|
||||||
this.index = index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PSBTEntry> getInputEntries() {
|
public List<PSBTEntry> getInputEntries(int psbtVersion) {
|
||||||
List<PSBTEntry> entries = new ArrayList<>();
|
List<PSBTEntry> entries = new ArrayList<>();
|
||||||
|
|
||||||
if(nonWitnessUtxo != null) {
|
if(nonWitnessUtxo != null) {
|
||||||
|
@ -296,6 +392,32 @@ public class PSBTInput {
|
||||||
entries.add(populateEntry(PSBT_IN_POR_COMMITMENT, null, porCommitment.getBytes(StandardCharsets.UTF_8)));
|
entries.add(populateEntry(PSBT_IN_POR_COMMITMENT, null, porCommitment.getBytes(StandardCharsets.UTF_8)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(psbtVersion >= 2) {
|
||||||
|
if(prevTxid != null) {
|
||||||
|
entries.add(populateEntry(PSBT_IN_PREVIOUS_TXID, null, prevTxid.getBytes()));
|
||||||
|
}
|
||||||
|
if(prevIndex != null) {
|
||||||
|
byte[] prevIndexBytes = new byte[4];
|
||||||
|
Utils.uint32ToByteArrayLE(prevIndex, prevIndexBytes, 0);
|
||||||
|
entries.add(populateEntry(PSBT_IN_OUTPUT_INDEX, null, prevIndexBytes));
|
||||||
|
}
|
||||||
|
if(sequence != null) {
|
||||||
|
byte[] sequenceBytes = new byte[4];
|
||||||
|
Utils.uint32ToByteArrayLE(sequence, sequenceBytes, 0);
|
||||||
|
entries.add(populateEntry(PSBT_IN_SEQUENCE, null, sequenceBytes));
|
||||||
|
}
|
||||||
|
if(requiredTimeLocktime != null) {
|
||||||
|
byte[] requiredTimeLocktimeBytes = new byte[4];
|
||||||
|
Utils.uint32ToByteArrayLE(requiredTimeLocktime, requiredTimeLocktimeBytes, 0);
|
||||||
|
entries.add(populateEntry(PSBT_IN_REQUIRED_TIME_LOCKTIME, null, requiredTimeLocktimeBytes));
|
||||||
|
}
|
||||||
|
if(requiredHeightLocktime != null) {
|
||||||
|
byte[] requiredHeightLocktimeBytes = new byte[4];
|
||||||
|
Utils.uint32ToByteArrayLE(requiredHeightLocktime, requiredHeightLocktimeBytes, 0);
|
||||||
|
entries.add(populateEntry(PSBT_IN_REQUIRED_HEIGHT_LOCKTIME, null, requiredHeightLocktimeBytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(Map.Entry<String, String> entry : proprietary.entrySet()) {
|
for(Map.Entry<String, String> entry : proprietary.entrySet()) {
|
||||||
entries.add(populateEntry(PSBT_IN_PROPRIETARY, Utils.hexToBytes(entry.getKey()), Utils.hexToBytes(entry.getValue())));
|
entries.add(populateEntry(PSBT_IN_PROPRIETARY, Utils.hexToBytes(entry.getKey()), Utils.hexToBytes(entry.getValue())));
|
||||||
}
|
}
|
||||||
|
@ -346,6 +468,42 @@ public class PSBTInput {
|
||||||
porCommitment = psbtInput.porCommitment;
|
porCommitment = psbtInput.porCommitment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(psbtInput.ripeMd160Preimage != null) {
|
||||||
|
ripeMd160Preimage = psbtInput.ripeMd160Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(psbtInput.sha256Preimage != null) {
|
||||||
|
sha256Preimage = psbtInput.sha256Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(psbtInput.hash160Preimage != null) {
|
||||||
|
hash160Preimage = psbtInput.hash160Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(psbtInput.hash256Preimage != null) {
|
||||||
|
hash256Preimage = psbtInput.hash256Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(psbtInput.prevTxid != null) {
|
||||||
|
prevTxid = psbtInput.prevTxid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(psbtInput.prevIndex != null) {
|
||||||
|
prevIndex = psbtInput.prevIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(psbtInput.sequence != null) {
|
||||||
|
sequence = psbtInput.sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(psbtInput.requiredTimeLocktime != null) {
|
||||||
|
requiredTimeLocktime = psbtInput.requiredTimeLocktime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(psbtInput.requiredHeightLocktime != null) {
|
||||||
|
requiredHeightLocktime = psbtInput.requiredHeightLocktime;
|
||||||
|
}
|
||||||
|
|
||||||
proprietary.putAll(psbtInput.proprietary);
|
proprietary.putAll(psbtInput.proprietary);
|
||||||
|
|
||||||
if(psbtInput.tapKeyPathSignature != null) {
|
if(psbtInput.tapKeyPathSignature != null) {
|
||||||
|
@ -481,6 +639,102 @@ public class PSBTInput {
|
||||||
return getUtxo() != null && getScriptType() == P2TR;
|
return getUtxo() != null && getScriptType() == P2TR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getRipeMd160Preimage() {
|
||||||
|
return ripeMd160Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRipeMd160Preimage(byte[] ripeMd160Preimage) {
|
||||||
|
this.ripeMd160Preimage = ripeMd160Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSha256Preimage() {
|
||||||
|
return sha256Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSha256Preimage(byte[] sha256Preimage) {
|
||||||
|
this.sha256Preimage = sha256Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getHash160Preimage() {
|
||||||
|
return hash160Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHash160Preimage(byte[] hash160Preimage) {
|
||||||
|
this.hash160Preimage = hash160Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getHash256Preimage() {
|
||||||
|
return hash256Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHash256Preimage(byte[] hash256Preimage) {
|
||||||
|
this.hash256Preimage = hash256Preimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sha256Hash getPrevTxid() {
|
||||||
|
if(psbt.getPsbtVersion() >= 2) {
|
||||||
|
return prevTxid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getInput().getOutpoint().getHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
Sha256Hash prevTxid() {
|
||||||
|
return prevTxid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrevTxid(Sha256Hash prevTxid) {
|
||||||
|
this.prevTxid = prevTxid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getPrevIndex() {
|
||||||
|
if(psbt.getPsbtVersion() >= 2) {
|
||||||
|
return prevIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getInput().getOutpoint().getIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
Long prevIndex() {
|
||||||
|
return prevIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrevIndex(Long prevIndex) {
|
||||||
|
this.prevIndex = prevIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSequence() {
|
||||||
|
if(psbt.getPsbtVersion() >= 2) {
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getInput().getSequenceNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
Long sequence() {
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSequence(Long sequence) {
|
||||||
|
this.sequence = sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getRequiredTimeLocktime() {
|
||||||
|
return requiredTimeLocktime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequiredTimeLocktime(Long requiredTimeLocktime) {
|
||||||
|
this.requiredTimeLocktime = requiredTimeLocktime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getRequiredHeightLocktime() {
|
||||||
|
return requiredHeightLocktime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequiredHeightLocktime(Long requiredHeightLocktime) {
|
||||||
|
this.requiredHeightLocktime = requiredHeightLocktime;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSigned() {
|
public boolean isSigned() {
|
||||||
if(getTapKeyPathSignature() != null) {
|
if(getTapKeyPathSignature() != null) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -676,7 +930,7 @@ public class PSBTInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
public TransactionInput getInput() {
|
public TransactionInput getInput() {
|
||||||
return transaction.getInputs().get(index);
|
return psbt.getTransaction().getInputs().get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TransactionOutput getUtxo() {
|
public TransactionOutput getUtxo() {
|
||||||
|
@ -705,12 +959,12 @@ public class PSBTInput {
|
||||||
ScriptType scriptType = getScriptType();
|
ScriptType scriptType = getScriptType();
|
||||||
if(scriptType == ScriptType.P2TR) {
|
if(scriptType == ScriptType.P2TR) {
|
||||||
List<TransactionOutput> spentUtxos = psbt.getPsbtInputs().stream().map(PSBTInput::getUtxo).collect(Collectors.toList());
|
List<TransactionOutput> spentUtxos = psbt.getPsbtInputs().stream().map(PSBTInput::getUtxo).collect(Collectors.toList());
|
||||||
hash = transaction.hashForTaprootSignature(spentUtxos, index, !P2TR.isScriptType(connectedScript), connectedScript, localSigHash, null);
|
hash = psbt.getTransaction().hashForTaprootSignature(spentUtxos, index, !P2TR.isScriptType(connectedScript), connectedScript, localSigHash, null);
|
||||||
} else if(Arrays.asList(WITNESS_TYPES).contains(scriptType)) {
|
} else if(Arrays.asList(WITNESS_TYPES).contains(scriptType)) {
|
||||||
long prevValue = getUtxo().getValue();
|
long prevValue = getUtxo().getValue();
|
||||||
hash = transaction.hashForWitnessSignature(index, connectedScript, prevValue, localSigHash);
|
hash = psbt.getTransaction().hashForWitnessSignature(index, connectedScript, prevValue, localSigHash);
|
||||||
} else {
|
} else {
|
||||||
hash = transaction.hashForLegacySignature(index, connectedScript, localSigHash);
|
hash = psbt.getTransaction().hashForLegacySignature(index, connectedScript, localSigHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash;
|
return hash;
|
||||||
|
|
|
@ -3,9 +3,7 @@ package com.sparrowwallet.drongo.psbt;
|
||||||
import com.sparrowwallet.drongo.KeyDerivation;
|
import com.sparrowwallet.drongo.KeyDerivation;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
import com.sparrowwallet.drongo.protocol.Script;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -18,6 +16,8 @@ public class PSBTOutput {
|
||||||
public static final byte PSBT_OUT_REDEEM_SCRIPT = 0x00;
|
public static final byte PSBT_OUT_REDEEM_SCRIPT = 0x00;
|
||||||
public static final byte PSBT_OUT_WITNESS_SCRIPT = 0x01;
|
public static final byte PSBT_OUT_WITNESS_SCRIPT = 0x01;
|
||||||
public static final byte PSBT_OUT_BIP32_DERIVATION = 0x02;
|
public static final byte PSBT_OUT_BIP32_DERIVATION = 0x02;
|
||||||
|
public static final byte PSBT_OUT_AMOUNT = 0x03;
|
||||||
|
public static final byte PSBT_OUT_SCRIPT = 0x04;
|
||||||
public static final byte PSBT_OUT_TAP_INTERNAL_KEY = 0x05;
|
public static final byte PSBT_OUT_TAP_INTERNAL_KEY = 0x05;
|
||||||
public static final byte PSBT_OUT_TAP_BIP32_DERIVATION = 0x07;
|
public static final byte PSBT_OUT_TAP_BIP32_DERIVATION = 0x07;
|
||||||
public static final byte PSBT_OUT_PROPRIETARY = (byte)0xfc;
|
public static final byte PSBT_OUT_PROPRIETARY = (byte)0xfc;
|
||||||
|
@ -29,13 +29,23 @@ public class PSBTOutput {
|
||||||
private Map<ECKey, Map<KeyDerivation, List<Sha256Hash>>> tapDerivedPublicKeys = new LinkedHashMap<>();
|
private Map<ECKey, Map<KeyDerivation, List<Sha256Hash>>> tapDerivedPublicKeys = new LinkedHashMap<>();
|
||||||
private ECKey tapInternalKey;
|
private ECKey tapInternalKey;
|
||||||
|
|
||||||
|
//PSBTv2 fields
|
||||||
|
private Long amount;
|
||||||
|
private Script script;
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(PSBTOutput.class);
|
private static final Logger log = LoggerFactory.getLogger(PSBTOutput.class);
|
||||||
|
|
||||||
PSBTOutput() {
|
private final PSBT psbt;
|
||||||
//empty constructor
|
private int index;
|
||||||
|
|
||||||
|
PSBTOutput(PSBT psbt, int index) {
|
||||||
|
this.psbt = psbt;
|
||||||
|
this.index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
PSBTOutput(ScriptType scriptType, Script redeemScript, Script witnessScript, Map<ECKey, KeyDerivation> derivedPublicKeys, Map<String, String> proprietary, ECKey tapInternalKey) {
|
PSBTOutput(PSBT psbt, int index, ScriptType scriptType, Script redeemScript, Script witnessScript, Map<ECKey, KeyDerivation> derivedPublicKeys, Map<String, String> proprietary, ECKey tapInternalKey) {
|
||||||
|
this(psbt, index);
|
||||||
|
|
||||||
this.redeemScript = redeemScript;
|
this.redeemScript = redeemScript;
|
||||||
this.witnessScript = witnessScript;
|
this.witnessScript = witnessScript;
|
||||||
|
|
||||||
|
@ -53,9 +63,10 @@ public class PSBTOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PSBTOutput(List<PSBTEntry> outputEntries) throws PSBTParseException {
|
PSBTOutput(PSBT psbt, List<PSBTEntry> outputEntries, int index) throws PSBTParseException {
|
||||||
|
this(psbt, index);
|
||||||
for(PSBTEntry entry : outputEntries) {
|
for(PSBTEntry entry : outputEntries) {
|
||||||
switch (entry.getKeyType()) {
|
switch((byte)entry.getKeyType()) {
|
||||||
case PSBT_OUT_REDEEM_SCRIPT:
|
case PSBT_OUT_REDEEM_SCRIPT:
|
||||||
entry.checkOneByteKey();
|
entry.checkOneByteKey();
|
||||||
Script redeemScript = new Script(entry.getData());
|
Script redeemScript = new Script(entry.getData());
|
||||||
|
@ -75,6 +86,17 @@ public class PSBTOutput {
|
||||||
this.derivedPublicKeys.put(derivedPublicKey, keyDerivation);
|
this.derivedPublicKeys.put(derivedPublicKey, keyDerivation);
|
||||||
log.debug("Found output bip32_derivation with master fingerprint " + keyDerivation.getMasterFingerprint() + " at path " + keyDerivation.getDerivationPath() + " public key " + derivedPublicKey);
|
log.debug("Found output bip32_derivation with master fingerprint " + keyDerivation.getMasterFingerprint() + " at path " + keyDerivation.getDerivationPath() + " public key " + derivedPublicKey);
|
||||||
break;
|
break;
|
||||||
|
case PSBT_OUT_AMOUNT:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
this.amount = Utils.readInt64(entry.getData(), 0);
|
||||||
|
log.debug("Found output amount " + this.amount);
|
||||||
|
break;
|
||||||
|
case PSBT_OUT_SCRIPT:
|
||||||
|
entry.checkOneByteKey();
|
||||||
|
Script script = new Script(entry.getData());
|
||||||
|
this.script = script;
|
||||||
|
log.debug("Found output script hex " + Utils.bytesToHex(script.getProgram()) + " script " + script);
|
||||||
|
break;
|
||||||
case PSBT_OUT_PROPRIETARY:
|
case PSBT_OUT_PROPRIETARY:
|
||||||
proprietary.put(Utils.bytesToHex(entry.getKeyData()), Utils.bytesToHex(entry.getData()));
|
proprietary.put(Utils.bytesToHex(entry.getKeyData()), Utils.bytesToHex(entry.getData()));
|
||||||
log.debug("Found proprietary output " + Utils.bytesToHex(entry.getKeyData()) + ": " + Utils.bytesToHex(entry.getData()));
|
log.debug("Found proprietary output " + Utils.bytesToHex(entry.getKeyData()) + ": " + Utils.bytesToHex(entry.getData()));
|
||||||
|
@ -103,7 +125,7 @@ public class PSBTOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PSBTEntry> getOutputEntries() {
|
public List<PSBTEntry> getOutputEntries(int psbtVersion) {
|
||||||
List<PSBTEntry> entries = new ArrayList<>();
|
List<PSBTEntry> entries = new ArrayList<>();
|
||||||
|
|
||||||
if(redeemScript != null) {
|
if(redeemScript != null) {
|
||||||
|
@ -118,6 +140,17 @@ public class PSBTOutput {
|
||||||
entries.add(populateEntry(PSBT_OUT_BIP32_DERIVATION, entry.getKey().getPubKey(), serializeKeyDerivation(entry.getValue())));
|
entries.add(populateEntry(PSBT_OUT_BIP32_DERIVATION, entry.getKey().getPubKey(), serializeKeyDerivation(entry.getValue())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(psbtVersion >= 2) {
|
||||||
|
if(amount != null) {
|
||||||
|
byte[] amountBytes = new byte[64];
|
||||||
|
Utils.int64ToByteArrayLE(amount, amountBytes, 0);
|
||||||
|
entries.add(populateEntry(PSBT_OUT_AMOUNT, null, amountBytes));
|
||||||
|
}
|
||||||
|
if(script != null) {
|
||||||
|
entries.add(populateEntry(PSBT_OUT_SCRIPT, null, script.getProgram()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(Map.Entry<String, String> entry : proprietary.entrySet()) {
|
for(Map.Entry<String, String> entry : proprietary.entrySet()) {
|
||||||
entries.add(populateEntry(PSBT_OUT_PROPRIETARY, Utils.hexToBytes(entry.getKey()), Utils.hexToBytes(entry.getValue())));
|
entries.add(populateEntry(PSBT_OUT_PROPRIETARY, Utils.hexToBytes(entry.getKey()), Utils.hexToBytes(entry.getValue())));
|
||||||
}
|
}
|
||||||
|
@ -145,6 +178,15 @@ public class PSBTOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
derivedPublicKeys.putAll(psbtOutput.derivedPublicKeys);
|
derivedPublicKeys.putAll(psbtOutput.derivedPublicKeys);
|
||||||
|
|
||||||
|
if(psbtOutput.amount != null) {
|
||||||
|
amount = psbtOutput.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(psbtOutput.script != null) {
|
||||||
|
script = psbtOutput.script;
|
||||||
|
}
|
||||||
|
|
||||||
proprietary.putAll(psbtOutput.proprietary);
|
proprietary.putAll(psbtOutput.proprietary);
|
||||||
|
|
||||||
tapDerivedPublicKeys.putAll(psbtOutput.tapDerivedPublicKeys);
|
tapDerivedPublicKeys.putAll(psbtOutput.tapDerivedPublicKeys);
|
||||||
|
@ -178,6 +220,38 @@ public class PSBTOutput {
|
||||||
return derivedPublicKeys;
|
return derivedPublicKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getAmount() {
|
||||||
|
if(psbt.getPsbtVersion() >= 2) {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getOutput().getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
Long amount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmount(Long amount) {
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Script getScript() {
|
||||||
|
if(psbt.getPsbtVersion() >= 2) {
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getOutput().getScript();
|
||||||
|
}
|
||||||
|
|
||||||
|
Script script() {
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScript(Script script) {
|
||||||
|
this.script = script;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getProprietary() {
|
public Map<String, String> getProprietary() {
|
||||||
return proprietary;
|
return proprietary;
|
||||||
}
|
}
|
||||||
|
@ -198,6 +272,14 @@ public class PSBTOutput {
|
||||||
this.tapInternalKey = tapInternalKey;
|
this.tapInternalKey = tapInternalKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TransactionOutput getOutput() {
|
||||||
|
return psbt.getTransaction().getOutputs().get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setIndex(int index) {
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
public void clearNonFinalFields() {
|
public void clearNonFinalFields() {
|
||||||
tapDerivedPublicKeys.clear();
|
tapDerivedPublicKeys.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,7 +207,7 @@ public class PSBTTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validUnknownInputs() throws PSBTParseException {
|
public void validUnknownInputs() throws PSBTParseException {
|
||||||
String psbt = "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=";
|
String psbt = "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=";
|
||||||
PSBT psbt1 = PSBT.fromString(psbt);
|
PSBT psbt1 = PSBT.fromString(psbt);
|
||||||
|
|
||||||
Assertions.assertEquals(1, psbt1.getPsbtInputs().size());
|
Assertions.assertEquals(1, psbt1.getPsbtInputs().size());
|
||||||
|
@ -406,6 +406,318 @@ public class PSBTTest {
|
||||||
Assertions.assertEquals("3fd9781d", tapOutKeyDerivations.keySet().iterator().next().getMasterFingerprint());
|
Assertions.assertEquals("3fd9781d", tapOutKeyDerivations.keySet().iterator().next().getMasterFingerprint());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0InvalidVersion() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAH7BAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_GLOBAL_UNSIGNED_TX is not allowed in PSBTv2", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0TxVersion() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAECBAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_GLOBAL_TX_VERSION is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0FallbackLocktime() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEDBAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_GLOBAL_FALLBACK_LOCKTIME is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0InputCount() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEEAQIAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_GLOBAL_INPUT_COUNT is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0OutputCount() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEFAQIAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_GLOBAL_OUTPUT_COUNT is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0Modifiable() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEGAQAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_GLOBAL_TX_MODIFIABLE is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0PrevTxid() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gAIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAACICA27+LCVWIZhlU7qdZcPdxkFlyhQ24FqjWkxusCRRz3ltGPadhz5UAACAAQAAgAAAAIABAAAAYgAAAAA=";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_IN_PREV_TXID is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0OutputIndex() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_IN_OUTPUT_INDEX is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0Sequence() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonARAE/////wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_IN_SEQUENCE is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0RequiredTimeLocktime() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonAREEjI3EYgAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_IN_REQUIRED_TIME_LOCKTIME is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0RequiredHeightLocktime() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonARIEECcAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_IN_REQUIRED_HEIGHT_LOCKTIME is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0OutputAmount() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEDCAAIry8AAAAAACICA27+LCVWIZhlU7qdZcPdxkFlyhQ24FqjWkxusCRRz3ltGPadhz5UAACAAQAAgAAAAIABAAAAYgAAAAA=";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_OUT_AMOUNT is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv0OutputScript() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEEFgAUoH2sirbKlC03nteV+DW6ccnMaIUAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_OUT_SCRIPT is not allowed in PSBTv0", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv2MissingInputCount() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_GLOBAL_INPUT_COUNT is required in PSBTv2", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv2MissingOutputCount() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt));
|
||||||
|
Assertions.assertEquals("PSBT_GLOBAL_OUTPUT_COUNT is required in PSBTv2", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv2MissingPrevTxid() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEPBAAAAAABEAT+////ACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEDCAAIry8AAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAAiAgLjb7/1PdU0Bwz4/TlmFGgPNXqbhdtzQL8c+nRdKtezQBj2nYc+VAAAgAEAAIAAAACAAQAAAGQAAAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt, true, false));
|
||||||
|
Assertions.assertEquals("PSBT_IN_PREV_TXID is required in PSBTv2", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv2MissingOutputIndex() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt, true, false));
|
||||||
|
Assertions.assertEquals("PSBT_IN_OUTPUT_INDEX is required in PSBTv2", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv2MissingOutputAmount() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAEQBP7///8AIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAAiAgLjb7/1PdU0Bwz4/TlmFGgPNXqbhdtzQL8c+nRdKtezQBj2nYc+VAAAgAEAAIAAAACAAQAAAGQAAAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt, true, false));
|
||||||
|
Assertions.assertEquals("PSBT_OUT_AMOUNT is required in PSBTv2", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv2MissingOutputScript() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAEQBP7///8AIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQMIAAivLwAAAAAAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt, true, false));
|
||||||
|
Assertions.assertEquals("PSBT_OUT_SCRIPT is required in PSBTv2", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv2SmallTimeLocktime() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAAREE/2TNHQAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt, true, false));
|
||||||
|
Assertions.assertEquals("Required time locktime is less than 500000000", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv2LargeHeightLocktime() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARIEAGXNHQAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
Exception e = Assertions.assertThrows(PSBTParseException.class, () -> PSBT.fromString(strPsbt, true, false));
|
||||||
|
Assertions.assertEquals("Required time locktime is greater than or equal to 500000000", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv21Input2OutputsRequiredOnly() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt);
|
||||||
|
Assertions.assertEquals(1, psbt.getInputCount());
|
||||||
|
Assertions.assertEquals(2, psbt.getOutputCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv21Input2OutputsUpdated() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEDCAAIry8AAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAAiAgLjb7/1PdU0Bwz4/TlmFGgPNXqbhdtzQL8c+nRdKtezQBj2nYc+VAAAgAEAAIAAAACAAQAAAGQAAAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(1, psbt.getInputCount());
|
||||||
|
Assertions.assertEquals(2, psbt.getOutputCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv21Input2OutputsSequence() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(1, psbt.getInputCount());
|
||||||
|
Assertions.assertEquals(2, psbt.getOutputCount());
|
||||||
|
Assertions.assertEquals(4294967294L, psbt.getPsbtInputs().get(0).getSequence());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv21Input2OutputsSequenceLocktimes() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAEQBP7///8BEQSMjcRiARIEECcAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(1, psbt.getInputCount());
|
||||||
|
Assertions.assertEquals(2, psbt.getOutputCount());
|
||||||
|
Assertions.assertEquals(4294967294L, psbt.getPsbtInputs().get(0).getSequence());
|
||||||
|
Assertions.assertEquals(1657048460, psbt.getPsbtInputs().get(0).getRequiredTimeLocktime());
|
||||||
|
Assertions.assertEquals(10000, psbt.getPsbtInputs().get(0).getRequiredHeightLocktime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv21Input2OutputsModifiableBit0() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEBAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(1, psbt.getInputCount());
|
||||||
|
Assertions.assertEquals(2, psbt.getOutputCount());
|
||||||
|
Assertions.assertEquals((byte)0x01, psbt.getModifiable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv21Input2OutputsModifiableBit1() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(1, psbt.getInputCount());
|
||||||
|
Assertions.assertEquals(2, psbt.getOutputCount());
|
||||||
|
Assertions.assertEquals((byte)0x02, psbt.getModifiable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv21Input2OutputsModifiableAllBits() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEHAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(1, psbt.getInputCount());
|
||||||
|
Assertions.assertEquals(2, psbt.getOutputCount());
|
||||||
|
Assertions.assertEquals((byte)0x07, psbt.getModifiable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv21Input2OutputsAllFields() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAQYBBwH7BAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BDiALCtkhQZwchxlzXXLcc5+eqeBjjR/kwe7w+ZRAhIFfyAEPBAAAAAABEAT+////AREEjI3EYgESBBAnAAAAIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQMIAAivLwAAAAABBBYAFMQw9kxHVtoxDb0aCFVy7ymZJicsACICAuNvv/U91TQHDPj9OWYUaA81epuF23NAvxz6dF0q17NAGPadhz5UAACAAQAAgAAAAIABAAAAZAAAAAEDCIu96wsAAAAAAQQWABRN0ZOslkpWrBueHMqEVP4vR0+FEwA=";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(1, psbt.getInputCount());
|
||||||
|
Assertions.assertEquals(2, psbt.getOutputCount());
|
||||||
|
Assertions.assertEquals((byte)0x07, psbt.getModifiable());
|
||||||
|
Assertions.assertEquals(4294967294L, psbt.getPsbtInputs().get(0).getSequence());
|
||||||
|
Assertions.assertEquals(1657048460, psbt.getPsbtInputs().get(0).getRequiredTimeLocktime());
|
||||||
|
Assertions.assertEquals(10000, psbt.getPsbtInputs().get(0).getRequiredHeightLocktime());
|
||||||
|
Assertions.assertEquals(800000000, psbt.getPsbtOutputs().get(0).getAmount());
|
||||||
|
Assertions.assertEquals(new Script(Utils.hexToBytes("0014c430f64c4756da310dbd1a085572ef299926272c")), psbt.getPsbtOutputs().get(0).getScript());
|
||||||
|
Assertions.assertEquals(199998859, psbt.getPsbtOutputs().get(1).getAmount());
|
||||||
|
Assertions.assertEquals(new Script(Utils.hexToBytes("00144dd193ac964a56ac1b9e1cca8454fe2f474f8513")), psbt.getPsbtOutputs().get(1).getScript());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv2NoLocktime() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(0, psbt.getTransaction().getLocktime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv2ZeroFallbackLocktime() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(0, psbt.getFallbackLocktime());
|
||||||
|
Assertions.assertEquals(0, psbt.getTransaction().getLocktime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv21InputLocktime() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(10000, psbt.getTransaction().getLocktime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv22InputsLocktimeHeight() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAESBCgjAAAAAQMIT5M1dwAAAAABBBYAFAsTUsrNA89qobfzyNY4hnGzSl4RAA==";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(10000, psbt.getTransaction().getLocktime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv22InputsLocktimeMixed() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(10000, psbt.getTransaction().getLocktime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv22InputsLocktimeMixed2() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiARIEKCMAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(10000, psbt.getTransaction().getLocktime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv22InputsLocktimeTimeMixed() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(1657048460, psbt.getTransaction().getLocktime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv22InputsLocktimeTimeMixed2() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(1657048460, psbt.getTransaction().getLocktime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPSBTv22InputsLocktimeTimeMixed3() throws PSBTParseException {
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAREEjI3EYgABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA";
|
||||||
|
PSBT psbt = PSBT.fromString(strPsbt, true, false);
|
||||||
|
Assertions.assertEquals(1657048460, psbt.getTransaction().getLocktime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertPSBTv0ToPSBTv2() throws PSBTParseException {
|
||||||
|
Network.set(Network.TESTNET);
|
||||||
|
String strPsbt = "cHNidP8BAH0CAAAAAdPUSBYKaQKOqAMgU2IcGuM6z7JtbkLe69OmYZoa4UxYAAAAAADmdQAAAp4CAAAAAAAAFgAUg+/zyGWbtKbIJb8ZasbGYDtItZhgrgEAAAAAACJRIJMQKWeQsI/WiRS+lmeeyeJaKFVnlVoBtXeuGTO7XIbrAAAAAE8BBDWHzwM27GqkgAAAAO1YZge2AP67ozhdoF9wgg2hpJw1jbVEXLKfxRQUNGEIAlog/wK83w7jxD37prhWrPenLxjGAJzkJKrj6h0ZPK8WED/ZeB1WAACAAQAAgAAAAIAAAQCJAgAAAAGBuBuu5OIheS4SKtYJufScCJDTWWLopBoXtFWhPDuRWQEAAAAA/f///wKNsQEAAAAAACJRIAjXFSnHwcD+J/obgd9CxVneHsUyFKM9xU7NY5K3DgCxHwIAAAAAAAAiUSAhUe66hJMCB1esHqXtxRVJHmviQ4ZjzFIwDWDPk+1KY6VyIQABASuNsQEAAAAAACJRIAjXFSnHwcD+J/obgd9CxVneHsUyFKM9xU7NY5K3DgCxAQMEAAAAAAETQCG/ZGuefjDVqBhmgVEuV1HbdxoZKDDWWTvTUrq6MJreRzj22k/WcFni6yPn9PGZkptZSNx9waf8ouP28ogJz24hFnRZPMvN82XI+lnim7dRKwFgpHnqiDoMGnIoFoSQRX7bGQA/2XgdVgAAgAEAAIAAAACAAQAAAAIAAAABFyB0WTzLzfNlyPpZ4pu3USsBYKR56og6DBpyKBaEkEV+2wAAIQflpK6JYWolG3K2DU7FU3hlkwSdU/69bglhZxaprqSvyxkAP9l4HVYAAIABAACAAAAAgAEAAAADAAAAAQUg5aSuiWFqJRtytg1OxVN4ZZMEnVP+vW4JYWcWqa6kr8sA";
|
||||||
|
PSBT origPsbtv0 = PSBT.fromString(strPsbt);
|
||||||
|
PSBT psbtv0 = PSBT.fromString(strPsbt);
|
||||||
|
psbtv0.convertVersion(2);
|
||||||
|
PSBT psbtv2 = PSBT.fromString(psbtv0.toBase64String());
|
||||||
|
Assertions.assertEquals(origPsbtv0.getTransaction().getTxId(), psbtv2.getTransaction().getTxId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertPSBTv2ToPSBTv0() throws PSBTParseException {
|
||||||
|
Network.set(Network.TESTNET);
|
||||||
|
String strPsbt = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAQYBBwH7BAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BDiALCtkhQZwchxlzXXLcc5+eqeBjjR/kwe7w+ZRAhIFfyAEPBAAAAAABEAT+////AREEjI3EYgESBBAnAAAAIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQMIAAivLwAAAAABBBYAFMQw9kxHVtoxDb0aCFVy7ymZJicsACICAuNvv/U91TQHDPj9OWYUaA81epuF23NAvxz6dF0q17NAGPadhz5UAACAAQAAgAAAAIABAAAAZAAAAAEDCIu96wsAAAAAAQQWABRN0ZOslkpWrBueHMqEVP4vR0+FEwA=";
|
||||||
|
PSBT origPsbtv2 = PSBT.fromString(strPsbt, true, false);
|
||||||
|
PSBT psbtv2 = PSBT.fromString(strPsbt, true, false);
|
||||||
|
psbtv2.convertVersion(0);
|
||||||
|
PSBT psbtv0 = PSBT.fromString(psbtv2.toBase64String(), true, false);
|
||||||
|
Assertions.assertEquals(origPsbtv2.getTransaction().getTxId(), psbtv0.getTransaction().getTxId());
|
||||||
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() throws Exception {
|
||||||
Network.set(null);
|
Network.set(null);
|
||||||
|
|
Loading…
Reference in a new issue