mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 01:56:44 +00:00
refactor and initial tests
This commit is contained in:
parent
ce2b0648a6
commit
e5d2ad427c
7 changed files with 569 additions and 376 deletions
|
@ -2,6 +2,7 @@ package com.craigraw.drongo.protocol;
|
|||
|
||||
import com.craigraw.drongo.Utils;
|
||||
import com.craigraw.drongo.address.*;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
@ -101,6 +102,10 @@ public class Script {
|
|||
}
|
||||
}
|
||||
|
||||
public String getProgramAsHex() {
|
||||
return Hex.toHexString(getProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this script has the required form to contain a destination address
|
||||
*/
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package com.craigraw.drongo.protocol;
|
||||
|
||||
import com.craigraw.drongo.Utils;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class TransactionWitness {
|
||||
|
@ -35,4 +37,42 @@ public class TransactionWitness {
|
|||
stream.write(push);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
for (byte[] push : pushes) {
|
||||
if (push == null) {
|
||||
buffer.append("NULL");
|
||||
} else if (push.length == 0) {
|
||||
buffer.append("EMPTY");
|
||||
} else {
|
||||
buffer.append(Hex.toHexString(push));
|
||||
}
|
||||
buffer.append(" ");
|
||||
}
|
||||
|
||||
return buffer.toString().trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
TransactionWitness other = (TransactionWitness) o;
|
||||
if (pushes.size() != other.pushes.size()) return false;
|
||||
for (int i = 0; i < pushes.size(); i++) {
|
||||
if (!Arrays.equals(pushes.get(i), other.pushes.get(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = 1;
|
||||
for (byte[] push : pushes) {
|
||||
hashCode = 31 * hashCode + (push == null ? 0 : Arrays.hashCode(push));
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,6 @@ package com.craigraw.drongo.psbt;
|
|||
import com.craigraw.drongo.ExtendedPublicKey;
|
||||
import com.craigraw.drongo.KeyDerivation;
|
||||
import com.craigraw.drongo.Utils;
|
||||
import com.craigraw.drongo.crypto.ChildNumber;
|
||||
import com.craigraw.drongo.crypto.ECKey;
|
||||
import com.craigraw.drongo.crypto.LazyECPoint;
|
||||
import com.craigraw.drongo.protocol.*;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
@ -17,29 +14,14 @@ import java.nio.ByteBuffer;
|
|||
import java.nio.ByteOrder;
|
||||
import java.util.*;
|
||||
|
||||
import static com.craigraw.drongo.psbt.PSBTEntry.parseKeyDerivation;
|
||||
|
||||
public class PSBT {
|
||||
public static final byte PSBT_GLOBAL_UNSIGNED_TX = 0x00;
|
||||
public static final byte PSBT_GLOBAL_BIP32_PUBKEY = 0x01;
|
||||
public static final byte PSBT_GLOBAL_VERSION = (byte)0xfb;
|
||||
public static final byte PSBT_GLOBAL_PROPRIETARY = (byte)0xfc;
|
||||
|
||||
public static final byte PSBT_IN_NON_WITNESS_UTXO = 0x00;
|
||||
public static final byte PSBT_IN_WITNESS_UTXO = 0x01;
|
||||
public static final byte PSBT_IN_PARTIAL_SIG = 0x02;
|
||||
public static final byte PSBT_IN_SIGHASH_TYPE = 0x03;
|
||||
public static final byte PSBT_IN_REDEEM_SCRIPT = 0x04;
|
||||
public static final byte PSBT_IN_WITNESS_SCRIPT = 0x05;
|
||||
public static final byte PSBT_IN_BIP32_DERIVATION = 0x06;
|
||||
public static final byte PSBT_IN_FINAL_SCRIPTSIG = 0x07;
|
||||
public static final byte PSBT_IN_FINAL_SCRIPTWITNESS = 0x08;
|
||||
public static final byte PSBT_IN_POR_COMMITMENT = 0x09;
|
||||
public static final byte PSBT_IN_PROPRIETARY = (byte)0xfc;
|
||||
|
||||
public static final byte PSBT_OUT_REDEEM_SCRIPT = 0x00;
|
||||
public static final byte PSBT_OUT_WITNESS_SCRIPT = 0x01;
|
||||
public static final byte PSBT_OUT_BIP32_DERIVATION = 0x02;
|
||||
public static final byte PSBT_OUT_PROPRIETARY = (byte)0xfc;
|
||||
|
||||
public static final String PSBT_MAGIC = "70736274";
|
||||
|
||||
private static final int STATE_GLOBALS = 1;
|
||||
|
@ -51,11 +33,8 @@ public class PSBT {
|
|||
|
||||
private int inputs = 0;
|
||||
private int outputs = 0;
|
||||
private boolean parseOK = false;
|
||||
|
||||
private String strPSBT = null;
|
||||
private byte[] psbtBytes = null;
|
||||
private ByteBuffer psbtByteBuffer = null;
|
||||
private byte[] psbtBytes;
|
||||
|
||||
private Transaction transaction = null;
|
||||
private Integer version = null;
|
||||
|
@ -67,270 +46,120 @@ public class PSBT {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(PSBT.class);
|
||||
|
||||
public PSBT(String strPSBT) throws Exception {
|
||||
if (!isPSBT(strPSBT)) {
|
||||
log.debug("Provided string is not a PSBT");
|
||||
return;
|
||||
public PSBT(byte[] psbt) {
|
||||
this.psbtBytes = psbt;
|
||||
parse();
|
||||
}
|
||||
|
||||
if (Utils.isBase64(strPSBT) && !Utils.isHex(strPSBT)) {
|
||||
this.strPSBT = Hex.toHexString(Base64.decode(strPSBT));
|
||||
} else {
|
||||
this.strPSBT = strPSBT;
|
||||
}
|
||||
|
||||
psbtBytes = Hex.decode(this.strPSBT);
|
||||
psbtByteBuffer = ByteBuffer.wrap(psbtBytes);
|
||||
|
||||
read();
|
||||
}
|
||||
|
||||
public PSBT(byte[] psbt) throws Exception {
|
||||
this(Hex.toHexString(psbt));
|
||||
}
|
||||
|
||||
public void read() throws Exception {
|
||||
private void parse() {
|
||||
int seenInputs = 0;
|
||||
int seenOutputs = 0;
|
||||
|
||||
psbtBytes = Hex.decode(strPSBT);
|
||||
psbtByteBuffer = ByteBuffer.wrap(psbtBytes);
|
||||
|
||||
log.debug("--- ***** START ***** ---");
|
||||
log.debug("--- PSBT length:" + psbtBytes.length + "---");
|
||||
log.debug("--- parsing header ---");
|
||||
ByteBuffer psbtByteBuffer = ByteBuffer.wrap(psbtBytes);
|
||||
|
||||
byte[] magicBuf = new byte[4];
|
||||
psbtByteBuffer.get(magicBuf);
|
||||
if (!PSBT.PSBT_MAGIC.equalsIgnoreCase(Hex.toHexString(magicBuf))) {
|
||||
throw new Exception("Invalid magic value");
|
||||
if (!PSBT_MAGIC.equalsIgnoreCase(Hex.toHexString(magicBuf))) {
|
||||
throw new IllegalStateException("PSBT has invalid magic value");
|
||||
}
|
||||
|
||||
byte sep = psbtByteBuffer.get();
|
||||
if (sep != (byte) 0xff) {
|
||||
throw new Exception("Bad 0xff separator:" + Hex.toHexString(new byte[]{sep}));
|
||||
throw new IllegalStateException("PSBT has bad initial separator:" + Hex.toHexString(new byte[]{sep}));
|
||||
}
|
||||
|
||||
int currentState = STATE_GLOBALS;
|
||||
PSBTInput currentInput = new PSBTInput();
|
||||
PSBTOutput currentOutput = new PSBTOutput();
|
||||
List<PSBTEntry> globalEntries = new ArrayList<>();
|
||||
List<List<PSBTEntry>> inputEntryLists = new ArrayList<>();
|
||||
List<List<PSBTEntry>> outputEntryLists = new ArrayList<>();
|
||||
|
||||
List<PSBTEntry> inputEntries = new ArrayList<>();
|
||||
List<PSBTEntry> outputEntries = new ArrayList<>();
|
||||
|
||||
while (psbtByteBuffer.hasRemaining()) {
|
||||
if (currentState == STATE_GLOBALS) {
|
||||
log.debug("--- parsing globals ---");
|
||||
} else if (currentState == STATE_INPUTS) {
|
||||
log.debug("--- parsing inputs ---");
|
||||
} else if (currentState == STATE_OUTPUTS) {
|
||||
log.debug("--- parsing outputs ---");
|
||||
}
|
||||
|
||||
PSBTEntry entry = parse();
|
||||
if (entry == null) {
|
||||
log.debug("PSBT parse returned null entry");
|
||||
}
|
||||
PSBTEntry entry = parseEntry(psbtByteBuffer);
|
||||
|
||||
if(entry.getKey() == null) { // length == 0
|
||||
switch (currentState) {
|
||||
case STATE_GLOBALS:
|
||||
currentState = STATE_INPUTS;
|
||||
parseGlobalEntries(globalEntries);
|
||||
break;
|
||||
case STATE_INPUTS:
|
||||
psbtInputs.add(currentInput);
|
||||
currentInput = new PSBTInput();
|
||||
inputEntryLists.add(inputEntries);
|
||||
inputEntries = new ArrayList<>();
|
||||
|
||||
seenInputs++;
|
||||
if (seenInputs == inputs) {
|
||||
currentState = STATE_OUTPUTS;
|
||||
parseInputEntries(inputEntryLists);
|
||||
}
|
||||
break;
|
||||
case STATE_OUTPUTS:
|
||||
psbtOutputs.add(currentOutput);
|
||||
currentOutput = new PSBTOutput();
|
||||
outputEntryLists.add(outputEntries);
|
||||
outputEntries = new ArrayList<>();
|
||||
|
||||
seenOutputs++;
|
||||
if (seenOutputs == outputs) {
|
||||
currentState = STATE_END;
|
||||
parseOutputEntries(outputEntryLists);
|
||||
}
|
||||
break;
|
||||
case STATE_END:
|
||||
parseOK = true;
|
||||
break;
|
||||
default:
|
||||
log.debug("PSBT read is in unknown state");
|
||||
break;
|
||||
throw new IllegalStateException("PSBT read is in unknown state");
|
||||
}
|
||||
} else if (currentState == STATE_GLOBALS) {
|
||||
switch (entry.getKeyType()[0]) {
|
||||
case PSBT.PSBT_GLOBAL_UNSIGNED_TX:
|
||||
Transaction transaction = new Transaction(entry.getData());
|
||||
inputs = transaction.getInputs().size();
|
||||
outputs = transaction.getOutputs().size();
|
||||
log.debug("Transaction with txid: " + transaction.getTxId() + " version " + transaction.getVersion() + " size " + transaction.getMessageSize() + " locktime " + transaction.getLockTime());
|
||||
for(TransactionInput input: transaction.getInputs()) {
|
||||
log.debug(" Transaction input references txid: " + input.getOutpoint().getHash() + " vout " + input.getOutpoint().getIndex() + " with script " + input.getScript());
|
||||
}
|
||||
for(TransactionOutput output: transaction.getOutputs()) {
|
||||
log.debug(" Transaction output value: " + output.getValue() + " to addresses " + Arrays.asList(output.getScript().getToAddresses()) + " with script hex " + Hex.toHexString(output.getScript().getProgram()) + " to script " + output.getScript());
|
||||
}
|
||||
setTransaction(transaction);
|
||||
break;
|
||||
case PSBT.PSBT_GLOBAL_BIP32_PUBKEY:
|
||||
KeyDerivation keyDerivation = parseKeyDerivation(entry.getData());
|
||||
ExtendedPublicKey pubKey = ExtendedPublicKey.fromDescriptor(keyDerivation.getMasterFingerprint(), keyDerivation.getDerivationPath(), Base58.encodeChecked(entry.getKeyData()), null);
|
||||
addExtendedPublicKey(pubKey, keyDerivation);
|
||||
log.debug("Pubkey with master fingerprint " + pubKey.getMasterFingerprint() + " at path " + pubKey.getKeyDerivationPath() + ": " + pubKey.getExtendedPublicKey());
|
||||
break;
|
||||
case PSBT.PSBT_GLOBAL_VERSION:
|
||||
int version = (int)Utils.readUint32(entry.getData(), 0);
|
||||
setVersion(version);
|
||||
log.debug("PSBT version: " + version);
|
||||
break;
|
||||
case PSBT.PSBT_GLOBAL_PROPRIETARY:
|
||||
addProprietary(Hex.toHexString(entry.getKeyData()), Hex.toHexString(entry.getData()));
|
||||
log.debug("PSBT global proprietary data: " + Hex.toHexString(entry.getData()));
|
||||
break;
|
||||
default:
|
||||
log.debug("PSBT global not recognized key type: " + entry.getKeyType()[0]);
|
||||
break;
|
||||
}
|
||||
globalEntries.add(entry);
|
||||
} else if (currentState == STATE_INPUTS) {
|
||||
switch (entry.getKeyType()[0]) {
|
||||
case PSBT.PSBT_IN_NON_WITNESS_UTXO:
|
||||
Transaction nonWitnessTx = new Transaction(entry.getData());
|
||||
currentInput.setNonWitnessUtxo(nonWitnessTx);
|
||||
log.debug("Found input non witness utxo with txid: " + nonWitnessTx.getTxId() + " version " + nonWitnessTx.getVersion() + " size " + nonWitnessTx.getMessageSize() + " locktime " + nonWitnessTx.getLockTime());
|
||||
for(TransactionInput input: nonWitnessTx.getInputs()) {
|
||||
log.debug(" Transaction input references txid: " + input.getOutpoint().getHash() + " vout " + input.getOutpoint().getIndex() + " with script " + input.getScript());
|
||||
}
|
||||
for(TransactionOutput output: nonWitnessTx.getOutputs()) {
|
||||
log.debug(" Transaction output value: " + output.getValue() + " to addresses " + Arrays.asList(output.getScript().getToAddresses()) + " with script hex " + Hex.toHexString(output.getScript().getProgram()) + " to script " + output.getScript());
|
||||
}
|
||||
break;
|
||||
case PSBT.PSBT_IN_WITNESS_UTXO:
|
||||
TransactionOutput witnessTxOutput = new TransactionOutput(null, entry.getData(), 0);
|
||||
currentInput.setWitnessUtxo(witnessTxOutput);
|
||||
log.debug("Found input witness utxo amount " + witnessTxOutput.getValue() + " script hex " + Hex.toHexString(witnessTxOutput.getScript().getProgram()) + " script " + witnessTxOutput.getScript() + " addresses " + Arrays.asList(witnessTxOutput.getScript().getToAddresses()));
|
||||
break;
|
||||
case PSBT.PSBT_IN_PARTIAL_SIG:
|
||||
LazyECPoint sigPublicKey = new LazyECPoint(ECKey.CURVE.getCurve(), entry.getKeyData());
|
||||
currentInput.addPartialSignature(sigPublicKey, entry.getData());
|
||||
log.debug("Found input partial signature with public key " + sigPublicKey + " signature " + Hex.toHexString(entry.getData()));
|
||||
break;
|
||||
case PSBT.PSBT_IN_SIGHASH_TYPE:
|
||||
long sighashType = Utils.readUint32(entry.getData(), 0);
|
||||
Transaction.SigHash sigHash = Transaction.SigHash.fromInt((int)sighashType);
|
||||
currentInput.setSigHash(sigHash);
|
||||
log.debug("Found input sighash_type " + sigHash.toString());
|
||||
break;
|
||||
case PSBT.PSBT_IN_REDEEM_SCRIPT:
|
||||
Script redeemScript = new Script(entry.getData());
|
||||
currentInput.setRedeemScript(redeemScript);
|
||||
log.debug("Found input redeem script hex " + Hex.toHexString(redeemScript.getProgram()) + " script " + redeemScript);
|
||||
break;
|
||||
case PSBT.PSBT_IN_WITNESS_SCRIPT:
|
||||
Script witnessScript = new Script(entry.getData());
|
||||
currentInput.setWitnessScript(witnessScript);
|
||||
log.debug("Found input witness script hex " + Hex.toHexString(witnessScript.getProgram()) + " script " + witnessScript);
|
||||
break;
|
||||
case PSBT.PSBT_IN_BIP32_DERIVATION:
|
||||
LazyECPoint derivedPublicKey = new LazyECPoint(ECKey.CURVE.getCurve(), entry.getKeyData());
|
||||
KeyDerivation keyDerivation = parseKeyDerivation(entry.getData());
|
||||
currentInput.addDerivedPublicKey(derivedPublicKey, keyDerivation);
|
||||
log.debug("Found input bip32_derivation with master fingerprint " + keyDerivation.getMasterFingerprint() + " at path " + keyDerivation.getDerivationPath() + " public key " + derivedPublicKey);
|
||||
break;
|
||||
case PSBT.PSBT_IN_FINAL_SCRIPTSIG:
|
||||
Script finalScriptSig = new Script(entry.getData());
|
||||
currentInput.setFinalScriptSig(finalScriptSig);
|
||||
log.debug("Found input final scriptSig script hex " + Hex.toHexString(finalScriptSig.getProgram()) + " script " + finalScriptSig.toString());
|
||||
break;
|
||||
case PSBT.PSBT_IN_FINAL_SCRIPTWITNESS:
|
||||
Script finalScriptWitness = new Script(entry.getData());
|
||||
currentInput.setFinalScriptWitness(finalScriptWitness);
|
||||
log.debug("Found input final scriptWitness script hex " + Hex.toHexString(finalScriptWitness.getProgram()) + " script " + finalScriptWitness.toString());
|
||||
break;
|
||||
case PSBT.PSBT_IN_POR_COMMITMENT:
|
||||
String porMessage = new String(entry.getData(), "UTF-8");
|
||||
currentInput.setPorCommitment(porMessage);
|
||||
log.debug("Found input POR commitment message " + porMessage);
|
||||
break;
|
||||
case PSBT.PSBT_IN_PROPRIETARY:
|
||||
currentInput.addProprietary(Hex.toHexString(entry.getKeyData()), Hex.toHexString(entry.getData()));
|
||||
log.debug("Found proprietary input " + Hex.toHexString(entry.getKeyData()) + ": " + Hex.toHexString(entry.getData()));
|
||||
break;
|
||||
default:
|
||||
log.debug("PSBT input not recognized key type:" + entry.getKeyType()[0]);
|
||||
break;
|
||||
}
|
||||
inputEntries.add(entry);
|
||||
} else if (currentState == STATE_OUTPUTS) {
|
||||
switch (entry.getKeyType()[0]) {
|
||||
case PSBT.PSBT_OUT_REDEEM_SCRIPT:
|
||||
Script redeemScript = new Script(entry.getData());
|
||||
currentOutput.setRedeemScript(redeemScript);
|
||||
log.debug("Found output redeem script hex " + Hex.toHexString(redeemScript.getProgram()) + " script " + redeemScript);
|
||||
break;
|
||||
case PSBT.PSBT_OUT_WITNESS_SCRIPT:
|
||||
Script witnessScript = new Script(entry.getData());
|
||||
currentOutput.setWitnessScript(witnessScript);
|
||||
log.debug("Found output witness script hex " + Hex.toHexString(witnessScript.getProgram()) + " script " + witnessScript);
|
||||
break;
|
||||
case PSBT.PSBT_OUT_BIP32_DERIVATION:
|
||||
LazyECPoint publicKey = new LazyECPoint(ECKey.CURVE.getCurve(), entry.getKeyData());
|
||||
KeyDerivation keyDerivation = parseKeyDerivation(entry.getData());
|
||||
currentOutput.addDerivedPublicKey(publicKey, keyDerivation);
|
||||
log.debug("Found output bip32_derivation with master fingerprint " + keyDerivation.getMasterFingerprint() + " at path " + keyDerivation.getDerivationPath() + " public key " + publicKey);
|
||||
break;
|
||||
case PSBT.PSBT_OUT_PROPRIETARY:
|
||||
currentOutput.addProprietary(Hex.toHexString(entry.getKeyData()), Hex.toHexString(entry.getData()));
|
||||
log.debug("Found proprietary output " + Hex.toHexString(entry.getKeyData()) + ": " + Hex.toHexString(entry.getData()));
|
||||
break;
|
||||
default:
|
||||
log.debug("PSBT output not recognized key type:" + entry.getKeyType()[0]);
|
||||
break;
|
||||
}
|
||||
outputEntries.add(entry);
|
||||
} else {
|
||||
log.debug("PSBT structure invalid");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (currentState == STATE_END) {
|
||||
log.debug("--- ***** END ***** ---");
|
||||
throw new IllegalStateException("PSBT structure invalid");
|
||||
}
|
||||
}
|
||||
|
||||
private PSBTEntry parse() {
|
||||
if(currentState != STATE_END) {
|
||||
if(transaction == null) {
|
||||
throw new IllegalStateException("Missing transaction");
|
||||
}
|
||||
|
||||
if(currentState == STATE_INPUTS) {
|
||||
throw new IllegalStateException("Missing inputs");
|
||||
}
|
||||
|
||||
if(currentState == STATE_OUTPUTS) {
|
||||
throw new IllegalStateException("Missing outputs");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PSBTEntry parseEntry(ByteBuffer psbtByteBuffer) {
|
||||
PSBTEntry entry = new PSBTEntry();
|
||||
|
||||
try {
|
||||
int keyLen = PSBT.readCompactInt(psbtByteBuffer);
|
||||
log.debug("PSBT entry key length: " + keyLen);
|
||||
|
||||
if (keyLen == 0x00) {
|
||||
log.debug("PSBT entry separator 0x00");
|
||||
return entry;
|
||||
}
|
||||
|
||||
byte[] key = new byte[keyLen];
|
||||
psbtByteBuffer.get(key);
|
||||
log.debug("PSBT entry key: " + Hex.toHexString(key));
|
||||
|
||||
byte[] keyType = new byte[1];
|
||||
keyType[0] = key[0];
|
||||
log.debug("PSBT entry key type: " + Hex.toHexString(keyType));
|
||||
byte keyType = key[0];
|
||||
|
||||
byte[] keyData = null;
|
||||
if (key.length > 1) {
|
||||
keyData = new byte[key.length - 1];
|
||||
System.arraycopy(key, 1, keyData, 0, keyData.length);
|
||||
log.debug("PSBT entry key data: " + Hex.toHexString(keyData));
|
||||
}
|
||||
|
||||
int dataLen = PSBT.readCompactInt(psbtByteBuffer);
|
||||
log.debug("PSBT entry data length: " + dataLen);
|
||||
|
||||
byte[] data = new byte[dataLen];
|
||||
psbtByteBuffer.get(data);
|
||||
log.debug("PSBT entry data: " + Hex.toHexString(data));
|
||||
|
||||
entry.setKey(key);
|
||||
entry.setKeyType(keyType);
|
||||
|
@ -340,14 +169,13 @@ public class PSBT {
|
|||
return entry;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.debug("Error parsing PSBT entry", e);
|
||||
return null;
|
||||
throw new IllegalStateException("Error parsing PSBT entry", e);
|
||||
}
|
||||
}
|
||||
|
||||
private PSBTEntry populateEntry(byte type, byte[] keydata, byte[] data) throws Exception {
|
||||
PSBTEntry entry = new PSBTEntry();
|
||||
entry.setKeyType(new byte[]{type});
|
||||
entry.setKeyType(type);
|
||||
entry.setKey(new byte[]{type});
|
||||
if (keydata != null) {
|
||||
entry.setKeyData(keydata);
|
||||
|
@ -357,6 +185,89 @@ public class PSBT {
|
|||
return entry;
|
||||
}
|
||||
|
||||
private void parseGlobalEntries(List<PSBTEntry> globalEntries) {
|
||||
PSBTEntry duplicate = findDuplicateKey(globalEntries);
|
||||
if(duplicate != null) {
|
||||
throw new IllegalStateException("Found duplicate key for PSBT global: " + Hex.toHexString(duplicate.getKey()));
|
||||
}
|
||||
|
||||
for(PSBTEntry entry : globalEntries) {
|
||||
switch(entry.getKeyType()) {
|
||||
case PSBT_GLOBAL_UNSIGNED_TX:
|
||||
entry.checkOneByteKey();
|
||||
Transaction transaction = new Transaction(entry.getData());
|
||||
inputs = transaction.getInputs().size();
|
||||
outputs = transaction.getOutputs().size();
|
||||
log.debug("Transaction with txid: " + transaction.getTxId() + " version " + transaction.getVersion() + " size " + transaction.getMessageSize() + " locktime " + transaction.getLockTime());
|
||||
for(TransactionInput input: transaction.getInputs()) {
|
||||
if(input.getScript().getProgram().length != 0) {
|
||||
throw new IllegalStateException("Unsigned tx input does not have empty scriptSig");
|
||||
}
|
||||
log.debug(" Transaction input references txid: " + input.getOutpoint().getHash() + " vout " + input.getOutpoint().getIndex() + " with script " + input.getScript());
|
||||
}
|
||||
for(TransactionOutput output: transaction.getOutputs()) {
|
||||
log.debug(" Transaction output value: " + output.getValue() + " to addresses " + Arrays.asList(output.getScript().getToAddresses()) + " with script hex " + Hex.toHexString(output.getScript().getProgram()) + " to script " + output.getScript());
|
||||
}
|
||||
this.transaction = transaction;
|
||||
break;
|
||||
case PSBT_GLOBAL_BIP32_PUBKEY:
|
||||
entry.checkOneBytePlusXpubKey();
|
||||
KeyDerivation keyDerivation = parseKeyDerivation(entry.getData());
|
||||
ExtendedPublicKey pubKey = ExtendedPublicKey.fromDescriptor(keyDerivation.getMasterFingerprint(), keyDerivation.getDerivationPath(), Base58.encodeChecked(entry.getKeyData()), null);
|
||||
this.extendedPublicKeys.put(pubKey, keyDerivation);
|
||||
log.debug("Pubkey with master fingerprint " + pubKey.getMasterFingerprint() + " at path " + pubKey.getKeyDerivationPath() + ": " + pubKey.getExtendedPublicKey());
|
||||
break;
|
||||
case PSBT_GLOBAL_VERSION:
|
||||
entry.checkOneByteKey();
|
||||
int version = (int)Utils.readUint32(entry.getData(), 0);
|
||||
this.version = version;
|
||||
log.debug("PSBT version: " + version);
|
||||
break;
|
||||
case PSBT_GLOBAL_PROPRIETARY:
|
||||
globalProprietary.put(Hex.toHexString(entry.getKeyData()), Hex.toHexString(entry.getData()));
|
||||
log.debug("PSBT global proprietary data: " + Hex.toHexString(entry.getData()));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("PSBT global not recognized key type: " + entry.getKeyType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseInputEntries(List<List<PSBTEntry>> inputEntryLists) {
|
||||
for(List<PSBTEntry> inputEntries : inputEntryLists) {
|
||||
PSBTEntry duplicate = findDuplicateKey(inputEntries);
|
||||
if(duplicate != null) {
|
||||
throw new IllegalStateException("Found duplicate key for PSBT input: " + Hex.toHexString(duplicate.getKey()));
|
||||
}
|
||||
|
||||
PSBTInput input = new PSBTInput(inputEntries);
|
||||
this.psbtInputs.add(input);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseOutputEntries(List<List<PSBTEntry>> outputEntryLists) {
|
||||
for(List<PSBTEntry> outputEntries : outputEntryLists) {
|
||||
PSBTEntry duplicate = findDuplicateKey(outputEntries);
|
||||
if(duplicate != null) {
|
||||
throw new IllegalStateException("Found duplicate key for PSBT output: " + Hex.toHexString(duplicate.getKey()));
|
||||
}
|
||||
|
||||
PSBTOutput output = new PSBTOutput(outputEntries);
|
||||
this.psbtOutputs.add(output);
|
||||
}
|
||||
}
|
||||
|
||||
private PSBTEntry findDuplicateKey(List<PSBTEntry> entries) {
|
||||
Set checkSet = new HashSet();
|
||||
for(PSBTEntry entry: entries) {
|
||||
if(!checkSet.add(Hex.toHexString(entry.getKey())) ) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] serialize() throws IOException {
|
||||
ByteArrayOutputStream transactionbaos = new ByteArrayOutputStream();
|
||||
transaction.bitcoinSerialize(transactionbaos);
|
||||
|
@ -365,7 +276,7 @@ public class PSBT {
|
|||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
// magic
|
||||
baos.write(Hex.decode(PSBT.PSBT_MAGIC), 0, Hex.decode(PSBT.PSBT_MAGIC).length);
|
||||
baos.write(Hex.decode(PSBT_MAGIC), 0, Hex.decode(PSBT_MAGIC).length);
|
||||
// separator
|
||||
baos.write((byte) 0xff);
|
||||
|
||||
|
@ -412,8 +323,6 @@ public class PSBT {
|
|||
baos.write((byte) 0x00);
|
||||
|
||||
psbtBytes = baos.toByteArray();
|
||||
strPSBT = Hex.toHexString(psbtBytes);
|
||||
log.debug("Wrote PSBT: " + strPSBT);
|
||||
|
||||
return psbtBytes;
|
||||
}
|
||||
|
@ -430,20 +339,10 @@ public class PSBT {
|
|||
return transaction;
|
||||
}
|
||||
|
||||
public void setTransaction(Transaction transaction) {
|
||||
testIfNull(this.transaction);
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
public Integer getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Integer version) {
|
||||
testIfNull(this.version);
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public KeyDerivation getKeyDerivation(ExtendedPublicKey publicKey) {
|
||||
return extendedPublicKeys.get(publicKey);
|
||||
}
|
||||
|
@ -452,24 +351,6 @@ public class PSBT {
|
|||
return new ArrayList<ExtendedPublicKey>(extendedPublicKeys.keySet());
|
||||
}
|
||||
|
||||
public void addExtendedPublicKey(ExtendedPublicKey publicKey, KeyDerivation derivation) {
|
||||
if(extendedPublicKeys.containsKey(publicKey)) {
|
||||
throw new IllegalStateException("Duplicate public key in scope");
|
||||
}
|
||||
|
||||
this.extendedPublicKeys.put(publicKey, derivation);
|
||||
}
|
||||
|
||||
public void addProprietary(String key, String data) {
|
||||
globalProprietary.put(key, data);
|
||||
}
|
||||
|
||||
private void testIfNull(Object obj) {
|
||||
if(obj != null) {
|
||||
throw new IllegalStateException("Duplicate keys in scope");
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
try {
|
||||
return Hex.toHexString(serialize());
|
||||
|
@ -558,41 +439,6 @@ public class PSBT {
|
|||
return ret;
|
||||
}
|
||||
|
||||
public KeyDerivation parseKeyDerivation(byte[] data) {
|
||||
String masterFingerprint = getMasterFingerprint(Arrays.copyOfRange(data, 0, 4));
|
||||
List<ChildNumber> bip32pathList = readBIP32Derivation(Arrays.copyOfRange(data, 4, data.length));
|
||||
String bip32path = KeyDerivation.writePath(bip32pathList);
|
||||
return new KeyDerivation(masterFingerprint, bip32path);
|
||||
}
|
||||
|
||||
public static String getMasterFingerprint(byte[] data) {
|
||||
return Hex.toHexString(data);
|
||||
}
|
||||
|
||||
public static List<ChildNumber> readBIP32Derivation(byte[] data) {
|
||||
List<ChildNumber> path = new ArrayList<>();
|
||||
|
||||
ByteBuffer bb = ByteBuffer.wrap(data);
|
||||
byte[] buf = new byte[4];
|
||||
|
||||
do {
|
||||
bb.get(buf);
|
||||
reverse(buf);
|
||||
ByteBuffer pbuf = ByteBuffer.wrap(buf);
|
||||
path.add(new ChildNumber(pbuf.getInt()));
|
||||
} while(bb.hasRemaining());
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static void reverse(byte[] array) {
|
||||
for (int i = 0; i < array.length / 2; i++) {
|
||||
byte temp = array[i];
|
||||
array[i] = array[array.length - i - 1];
|
||||
array[array.length - i - 1] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] writeBIP32Derivation(byte[] fingerprint, int purpose, int type, int account, int chain, int index) {
|
||||
// fingerprint and integer values to BIP32 derivation buffer
|
||||
byte[] bip32buf = new byte[24];
|
||||
|
@ -634,17 +480,30 @@ public class PSBT {
|
|||
}
|
||||
|
||||
public static boolean isPSBT(String s) {
|
||||
if (Utils.isHex(s) && s.startsWith(PSBT.PSBT_MAGIC)) {
|
||||
if (Utils.isHex(s) && s.startsWith(PSBT_MAGIC)) {
|
||||
return true;
|
||||
} else if (Utils.isBase64(s) && Hex.toHexString(Base64.decode(s)).startsWith(PSBT.PSBT_MAGIC)) {
|
||||
} else if (Utils.isBase64(s) && Hex.toHexString(Base64.decode(s)).startsWith(PSBT_MAGIC)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static PSBT fromString(String strPSBT) {
|
||||
if (!isPSBT(strPSBT)) {
|
||||
throw new IllegalArgumentException("Provided string is not a PSBT");
|
||||
}
|
||||
|
||||
if (Utils.isBase64(strPSBT) && !Utils.isHex(strPSBT)) {
|
||||
strPSBT = Hex.toHexString(Base64.decode(strPSBT));
|
||||
}
|
||||
|
||||
byte[] psbtBytes = Hex.decode(strPSBT);
|
||||
return new PSBT(psbtBytes);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String psbtBase64 = "cHNidP8BAMkCAAAAA3lxWr8zSZt5tiGZegyFWmd8b62cew6qi/4rTZGGif8OAAAAAAD/////td4T4zmwdQ8R2SbwRjRj+alAy1VX8mYZD2o9ZmefNIsAAAAAAP////+k9Xvvp9Lpap1TWd51NWu+MIfojG+MCqmguPyjII+5YgAAAAAA/////wKMz/AIAAAAABl2qRSE7GtWKUoaFcVQ8n9qfMYi41Yh0YisjM/wCAAAAAAZdqkUmka3O8TiIRG8h+a1mDLFQVTfJEiIrAAAAAAAAQBVAgAAAAGt3gAAAAAAAO++AAAAAAAAAAAAAAAAAAAAAAAAAAAAAEkAAAAA/////wEA4fUFAAAAABl2qRSvQiRNb8B3El3G+KdspA3+DRvH1IisAAAAACIGA383lPO+TErMCGrITWkCwCVxPqv4iQ8g9ErPCzTjwPD3DHSXSzsAAAAAAAAAAAABAFUCAAAAAa3eAAAAAAAA774AAAAAAAAAAAAAAAAAAAAAAAAAAAAASQAAAAD/////AQDh9QUAAAAAGXapFAn8nw1IXPh34v8wuhJrcu34Xg8qiKwAAAAAIgYDTr6iJ7sP/u+0gz4wi+Muuc4IxEoJaGYedN/uqwmSfbgMdJdLOwAAAAABAAAAAAEAVQIAAAABrd4AAAAAAADvvgAAAAAAAAAAAAAAAAAAAAAAAAAAAABJAAAAAP////8BAOH1BQAAAAAZdqkUGMIzFJsgyFIYzDbThZ5S2zTnvRiIrAAAAAAiBgK7oYu+Z/kEK6XK3urdEDW2ngkwnXD1gZBjEgRW0wD7Igx0l0s7AAAAAAIAAAAAACICAyw+nsM8JYHohVqRsQ2qilEwjZPh+OkGPqkO2kYZczCZEHSXSzsMAAAAIgAAADcBAAAA";
|
||||
String psbtBase64 = "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA";
|
||||
|
||||
PSBT psbt = null;
|
||||
String filename = "default.psbt";
|
||||
|
@ -656,7 +515,7 @@ public class PSBT {
|
|||
stream.close();
|
||||
psbt = new PSBT(psbtBytes);
|
||||
} else {
|
||||
psbt = new PSBT(psbtBase64);
|
||||
psbt = PSBT.fromString(psbtBase64);
|
||||
}
|
||||
|
||||
System.out.println(psbt);
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
package com.craigraw.drongo.psbt;
|
||||
|
||||
import com.craigraw.drongo.KeyDerivation;
|
||||
import com.craigraw.drongo.crypto.ChildNumber;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class PSBTEntry {
|
||||
private byte[] key = null;
|
||||
private byte[] keyType = null;
|
||||
private byte keyType;
|
||||
private byte[] keyData = null;
|
||||
private byte[] data = null;
|
||||
|
||||
|
@ -14,11 +23,11 @@ public class PSBTEntry {
|
|||
this.key = key;
|
||||
}
|
||||
|
||||
public byte[] getKeyType() {
|
||||
public byte getKeyType() {
|
||||
return keyType;
|
||||
}
|
||||
|
||||
public void setKeyType(byte[] keyType) {
|
||||
public void setKeyType(byte keyType) {
|
||||
this.keyType = keyType;
|
||||
}
|
||||
|
||||
|
@ -37,4 +46,57 @@ public class PSBTEntry {
|
|||
public void setData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static KeyDerivation parseKeyDerivation(byte[] data) {
|
||||
String masterFingerprint = getMasterFingerprint(Arrays.copyOfRange(data, 0, 4));
|
||||
List<ChildNumber> bip32pathList = readBIP32Derivation(Arrays.copyOfRange(data, 4, data.length));
|
||||
String bip32path = KeyDerivation.writePath(bip32pathList);
|
||||
return new KeyDerivation(masterFingerprint, bip32path);
|
||||
}
|
||||
|
||||
public static String getMasterFingerprint(byte[] data) {
|
||||
return Hex.toHexString(data);
|
||||
}
|
||||
|
||||
public static List<ChildNumber> readBIP32Derivation(byte[] data) {
|
||||
List<ChildNumber> path = new ArrayList<>();
|
||||
|
||||
ByteBuffer bb = ByteBuffer.wrap(data);
|
||||
byte[] buf = new byte[4];
|
||||
|
||||
do {
|
||||
bb.get(buf);
|
||||
reverse(buf);
|
||||
ByteBuffer pbuf = ByteBuffer.wrap(buf);
|
||||
path.add(new ChildNumber(pbuf.getInt()));
|
||||
} while(bb.hasRemaining());
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static void reverse(byte[] array) {
|
||||
for (int i = 0; i < array.length / 2; i++) {
|
||||
byte temp = array[i];
|
||||
array[i] = array[array.length - i - 1];
|
||||
array[array.length - i - 1] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void checkOneByteKey() {
|
||||
if(this.getKey().length != 1) {
|
||||
throw new IllegalStateException("PSBT key type must be one byte");
|
||||
}
|
||||
}
|
||||
|
||||
public void checkOneBytePlusXpubKey() {
|
||||
if(this.getKey().length != 79) {
|
||||
throw new IllegalStateException("PSBT key type must be one byte");
|
||||
}
|
||||
}
|
||||
|
||||
public void checkOneBytePlusPubKey() {
|
||||
if(this.getKey().length != 34) {
|
||||
throw new IllegalStateException("PSBT key type must be one byte");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,38 @@
|
|||
package com.craigraw.drongo.psbt;
|
||||
|
||||
import com.craigraw.drongo.KeyDerivation;
|
||||
import com.craigraw.drongo.Utils;
|
||||
import com.craigraw.drongo.crypto.ECKey;
|
||||
import com.craigraw.drongo.crypto.LazyECPoint;
|
||||
import com.craigraw.drongo.protocol.Script;
|
||||
import com.craigraw.drongo.protocol.Transaction;
|
||||
import com.craigraw.drongo.protocol.TransactionInput;
|
||||
import com.craigraw.drongo.protocol.TransactionOutput;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.craigraw.drongo.psbt.PSBTEntry.parseKeyDerivation;
|
||||
|
||||
public class PSBTInput {
|
||||
public static final byte PSBT_IN_NON_WITNESS_UTXO = 0x00;
|
||||
public static final byte PSBT_IN_WITNESS_UTXO = 0x01;
|
||||
public static final byte PSBT_IN_PARTIAL_SIG = 0x02;
|
||||
public static final byte PSBT_IN_SIGHASH_TYPE = 0x03;
|
||||
public static final byte PSBT_IN_REDEEM_SCRIPT = 0x04;
|
||||
public static final byte PSBT_IN_WITNESS_SCRIPT = 0x05;
|
||||
public static final byte PSBT_IN_BIP32_DERIVATION = 0x06;
|
||||
public static final byte PSBT_IN_FINAL_SCRIPTSIG = 0x07;
|
||||
public static final byte PSBT_IN_FINAL_SCRIPTWITNESS = 0x08;
|
||||
public static final byte PSBT_IN_POR_COMMITMENT = 0x09;
|
||||
public static final byte PSBT_IN_PROPRIETARY = (byte)0xfc;
|
||||
|
||||
private Transaction nonWitnessUtxo;
|
||||
private TransactionOutput witnessUtxo;
|
||||
private Map<LazyECPoint, byte[]> partialSignatures = new LinkedHashMap<>();
|
||||
|
@ -22,109 +45,138 @@ public class PSBTInput {
|
|||
private String porCommitment;
|
||||
private Map<String, String> proprietary = new LinkedHashMap<>();
|
||||
|
||||
public Transaction getNonWitnessUtxo() {
|
||||
return nonWitnessUtxo;
|
||||
private static final Logger log = LoggerFactory.getLogger(PSBTInput.class);
|
||||
|
||||
PSBTInput(List<PSBTEntry> inputEntries) {
|
||||
for(PSBTEntry entry : inputEntries) {
|
||||
switch(entry.getKeyType()) {
|
||||
case PSBT_IN_NON_WITNESS_UTXO:
|
||||
entry.checkOneByteKey();
|
||||
Transaction nonWitnessTx = new Transaction(entry.getData());
|
||||
this.nonWitnessUtxo = nonWitnessTx;
|
||||
log.debug("Found input non witness utxo with txid: " + nonWitnessTx.getTxId() + " version " + nonWitnessTx.getVersion() + " size " + nonWitnessTx.getMessageSize() + " locktime " + nonWitnessTx.getLockTime());
|
||||
for(TransactionInput input: nonWitnessTx.getInputs()) {
|
||||
log.debug(" Transaction input references txid: " + input.getOutpoint().getHash() + " vout " + input.getOutpoint().getIndex() + " with script " + input.getScript());
|
||||
}
|
||||
for(TransactionOutput output: nonWitnessTx.getOutputs()) {
|
||||
log.debug(" Transaction output value: " + output.getValue() + " to addresses " + Arrays.asList(output.getScript().getToAddresses()) + " with script hex " + Hex.toHexString(output.getScript().getProgram()) + " to script " + output.getScript());
|
||||
}
|
||||
break;
|
||||
case PSBT_IN_WITNESS_UTXO:
|
||||
entry.checkOneByteKey();
|
||||
TransactionOutput witnessTxOutput = new TransactionOutput(null, entry.getData(), 0);
|
||||
this.witnessUtxo = witnessTxOutput;
|
||||
log.debug("Found input witness utxo amount " + witnessTxOutput.getValue() + " script hex " + Hex.toHexString(witnessTxOutput.getScript().getProgram()) + " script " + witnessTxOutput.getScript() + " addresses " + Arrays.asList(witnessTxOutput.getScript().getToAddresses()));
|
||||
break;
|
||||
case PSBT_IN_PARTIAL_SIG:
|
||||
entry.checkOneBytePlusPubKey();
|
||||
LazyECPoint sigPublicKey = new LazyECPoint(ECKey.CURVE.getCurve(), entry.getKeyData());
|
||||
this.partialSignatures.put(sigPublicKey, entry.getData());
|
||||
log.debug("Found input partial signature with public key " + sigPublicKey + " signature " + Hex.toHexString(entry.getData()));
|
||||
break;
|
||||
case PSBT_IN_SIGHASH_TYPE:
|
||||
entry.checkOneByteKey();
|
||||
long sighashType = Utils.readUint32(entry.getData(), 0);
|
||||
Transaction.SigHash sigHash = Transaction.SigHash.fromInt((int)sighashType);
|
||||
this.sigHash = sigHash;
|
||||
log.debug("Found input sighash_type " + sigHash.toString());
|
||||
break;
|
||||
case PSBT_IN_REDEEM_SCRIPT:
|
||||
entry.checkOneByteKey();
|
||||
Script redeemScript = new Script(entry.getData());
|
||||
this.redeemScript = redeemScript;
|
||||
log.debug("Found input redeem script hex " + Hex.toHexString(redeemScript.getProgram()) + " script " + redeemScript);
|
||||
break;
|
||||
case PSBT_IN_WITNESS_SCRIPT:
|
||||
entry.checkOneByteKey();
|
||||
Script witnessScript = new Script(entry.getData());
|
||||
this.witnessScript = witnessScript;
|
||||
log.debug("Found input witness script hex " + Hex.toHexString(witnessScript.getProgram()) + " script " + witnessScript);
|
||||
break;
|
||||
case PSBT_IN_BIP32_DERIVATION:
|
||||
entry.checkOneBytePlusPubKey();
|
||||
LazyECPoint derivedPublicKey = new LazyECPoint(ECKey.CURVE.getCurve(), entry.getKeyData());
|
||||
KeyDerivation keyDerivation = parseKeyDerivation(entry.getData());
|
||||
this.derivedPublicKeys.put(derivedPublicKey, keyDerivation);
|
||||
log.debug("Found input bip32_derivation with master fingerprint " + keyDerivation.getMasterFingerprint() + " at path " + keyDerivation.getDerivationPath() + " public key " + derivedPublicKey);
|
||||
break;
|
||||
case PSBT_IN_FINAL_SCRIPTSIG:
|
||||
entry.checkOneByteKey();
|
||||
Script finalScriptSig = new Script(entry.getData());
|
||||
this.finalScriptSig = finalScriptSig;
|
||||
log.debug("Found input final scriptSig script hex " + Hex.toHexString(finalScriptSig.getProgram()) + " script " + finalScriptSig.toString());
|
||||
break;
|
||||
case PSBT_IN_FINAL_SCRIPTWITNESS:
|
||||
entry.checkOneByteKey();
|
||||
Script finalScriptWitness = new Script(entry.getData());
|
||||
this.finalScriptWitness = finalScriptWitness;
|
||||
log.debug("Found input final scriptWitness script hex " + Hex.toHexString(finalScriptWitness.getProgram()) + " script " + finalScriptWitness.toString());
|
||||
break;
|
||||
case PSBT_IN_POR_COMMITMENT:
|
||||
entry.checkOneByteKey();
|
||||
String porMessage = new String(entry.getData(), StandardCharsets.UTF_8);
|
||||
this.porCommitment = porMessage;
|
||||
log.debug("Found input POR commitment message " + porMessage);
|
||||
break;
|
||||
case PSBT_IN_PROPRIETARY:
|
||||
this.proprietary.put(Hex.toHexString(entry.getKeyData()), Hex.toHexString(entry.getData()));
|
||||
log.debug("Found proprietary input " + Hex.toHexString(entry.getKeyData()) + ": " + Hex.toHexString(entry.getData()));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("PSBT input not recognized key type: " + entry.getKeyType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setNonWitnessUtxo(Transaction nonWitnessUtxo) {
|
||||
testIfNull(this.nonWitnessUtxo);
|
||||
this.nonWitnessUtxo = nonWitnessUtxo;
|
||||
public Transaction getNonWitnessUtxo() {
|
||||
return nonWitnessUtxo;
|
||||
}
|
||||
|
||||
public TransactionOutput getWitnessUtxo() {
|
||||
return witnessUtxo;
|
||||
}
|
||||
|
||||
public void setWitnessUtxo(TransactionOutput witnessUtxo) {
|
||||
testIfNull(this.witnessUtxo);
|
||||
this.witnessUtxo = witnessUtxo;
|
||||
}
|
||||
|
||||
public byte[] getPartialSignature(LazyECPoint publicKey) {
|
||||
return partialSignatures.get(publicKey);
|
||||
}
|
||||
|
||||
public void addPartialSignature(LazyECPoint publicKey, byte[] partialSignature) {
|
||||
if(partialSignatures.containsKey(publicKey)) {
|
||||
throw new IllegalStateException("Duplicate public key signature in scope");
|
||||
}
|
||||
|
||||
this.partialSignatures.put(publicKey, partialSignature);
|
||||
}
|
||||
|
||||
public Transaction.SigHash getSigHash() {
|
||||
return sigHash;
|
||||
}
|
||||
|
||||
public void setSigHash(Transaction.SigHash sigHash) {
|
||||
testIfNull(this.sigHash);
|
||||
this.sigHash = sigHash;
|
||||
}
|
||||
|
||||
public Script getRedeemScript() {
|
||||
return redeemScript;
|
||||
}
|
||||
|
||||
public void setRedeemScript(Script redeemScript) {
|
||||
testIfNull(this.redeemScript);
|
||||
this.redeemScript = redeemScript;
|
||||
}
|
||||
|
||||
public Script getWitnessScript() {
|
||||
return witnessScript;
|
||||
}
|
||||
|
||||
public void setWitnessScript(Script witnessScript) {
|
||||
testIfNull(this.witnessScript);
|
||||
this.witnessScript = witnessScript;
|
||||
}
|
||||
|
||||
public KeyDerivation getKeyDerivation(LazyECPoint publicKey) {
|
||||
return derivedPublicKeys.get(publicKey);
|
||||
}
|
||||
|
||||
public void addDerivedPublicKey(LazyECPoint publicKey, KeyDerivation derivation) {
|
||||
if(derivedPublicKeys.containsKey(publicKey)) {
|
||||
throw new IllegalStateException("Duplicate public key in scope");
|
||||
}
|
||||
|
||||
this.derivedPublicKeys.put(publicKey, derivation);
|
||||
}
|
||||
|
||||
public Script getFinalScriptSig() {
|
||||
return finalScriptSig;
|
||||
}
|
||||
|
||||
public void setFinalScriptSig(Script finalScriptSig) {
|
||||
testIfNull(this.finalScriptSig);
|
||||
this.finalScriptSig = finalScriptSig;
|
||||
}
|
||||
|
||||
public Script getFinalScriptWitness() {
|
||||
return finalScriptWitness;
|
||||
}
|
||||
|
||||
public void setFinalScriptWitness(Script finalScriptWitness) {
|
||||
testIfNull(this.finalScriptWitness);
|
||||
this.finalScriptWitness = finalScriptWitness;
|
||||
}
|
||||
|
||||
public String getPorCommitment() {
|
||||
return porCommitment;
|
||||
}
|
||||
|
||||
public void setPorCommitment(String porCommitment) {
|
||||
testIfNull(this.porCommitment);
|
||||
this.porCommitment = porCommitment;
|
||||
public Map<LazyECPoint, byte[]> getPartialSignatures() {
|
||||
return partialSignatures;
|
||||
}
|
||||
|
||||
public void addProprietary(String key, String data) {
|
||||
proprietary.put(key, data);
|
||||
public Map<LazyECPoint, KeyDerivation> getDerivedPublicKeys() {
|
||||
return derivedPublicKeys;
|
||||
}
|
||||
|
||||
private void testIfNull(Object obj) {
|
||||
if(obj != null) {
|
||||
throw new IllegalStateException("Duplicate keys in scope");
|
||||
}
|
||||
public Map<String, String> getProprietary() {
|
||||
return proprietary;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,55 +1,79 @@
|
|||
package com.craigraw.drongo.psbt;
|
||||
|
||||
import com.craigraw.drongo.KeyDerivation;
|
||||
import com.craigraw.drongo.crypto.ECKey;
|
||||
import com.craigraw.drongo.crypto.LazyECPoint;
|
||||
import com.craigraw.drongo.protocol.Script;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PSBTOutput {
|
||||
public static final byte PSBT_OUT_REDEEM_SCRIPT = 0x00;
|
||||
public static final byte PSBT_OUT_WITNESS_SCRIPT = 0x01;
|
||||
public static final byte PSBT_OUT_BIP32_DERIVATION = 0x02;
|
||||
public static final byte PSBT_OUT_PROPRIETARY = (byte)0xfc;
|
||||
|
||||
private Script redeemScript;
|
||||
private Script witnessScript;
|
||||
private Map<LazyECPoint, KeyDerivation> derivedPublicKeys = new LinkedHashMap<>();
|
||||
private Map<String, String> proprietary = new LinkedHashMap<>();
|
||||
|
||||
public Script getRedeemScript() {
|
||||
return redeemScript;
|
||||
private static final Logger log = LoggerFactory.getLogger(PSBTOutput.class);
|
||||
|
||||
PSBTOutput(List<PSBTEntry> outputEntries) {
|
||||
for(PSBTEntry entry : outputEntries) {
|
||||
switch (entry.getKeyType()) {
|
||||
case PSBT_OUT_REDEEM_SCRIPT:
|
||||
entry.checkOneByteKey();
|
||||
Script redeemScript = new Script(entry.getData());
|
||||
this.redeemScript = redeemScript;
|
||||
log.debug("Found output redeem script hex " + Hex.toHexString(redeemScript.getProgram()) + " script " + redeemScript);
|
||||
break;
|
||||
case PSBT_OUT_WITNESS_SCRIPT:
|
||||
entry.checkOneByteKey();
|
||||
Script witnessScript = new Script(entry.getData());
|
||||
this.witnessScript = witnessScript;
|
||||
log.debug("Found output witness script hex " + Hex.toHexString(witnessScript.getProgram()) + " script " + witnessScript);
|
||||
break;
|
||||
case PSBT_OUT_BIP32_DERIVATION:
|
||||
entry.checkOneBytePlusPubKey();
|
||||
LazyECPoint publicKey = new LazyECPoint(ECKey.CURVE.getCurve(), entry.getKeyData());
|
||||
KeyDerivation keyDerivation = PSBTEntry.parseKeyDerivation(entry.getData());
|
||||
this.derivedPublicKeys.put(publicKey, keyDerivation);
|
||||
log.debug("Found output bip32_derivation with master fingerprint " + keyDerivation.getMasterFingerprint() + " at path " + keyDerivation.getDerivationPath() + " public key " + publicKey);
|
||||
break;
|
||||
case PSBT_OUT_PROPRIETARY:
|
||||
proprietary.put(Hex.toHexString(entry.getKeyData()), Hex.toHexString(entry.getData()));
|
||||
log.debug("Found proprietary output " + Hex.toHexString(entry.getKeyData()) + ": " + Hex.toHexString(entry.getData()));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("PSBT output not recognized key type: " + entry.getKeyType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setRedeemScript(Script redeemScript) {
|
||||
testIfNull(this.redeemScript);
|
||||
this.redeemScript = redeemScript;
|
||||
public Script getRedeemScript() {
|
||||
return redeemScript;
|
||||
}
|
||||
|
||||
public Script getWitnessScript() {
|
||||
return witnessScript;
|
||||
}
|
||||
|
||||
public void setWitnessScript(Script witnessScript) {
|
||||
testIfNull(this.witnessScript);
|
||||
this.witnessScript = witnessScript;
|
||||
}
|
||||
|
||||
public KeyDerivation getKeyDerivation(LazyECPoint publicKey) {
|
||||
return derivedPublicKeys.get(publicKey);
|
||||
}
|
||||
|
||||
public void addDerivedPublicKey(LazyECPoint publicKey, KeyDerivation derivation) {
|
||||
if(derivedPublicKeys.containsKey(publicKey)) {
|
||||
throw new IllegalStateException("Duplicate public key in scope");
|
||||
public Map<LazyECPoint, KeyDerivation> getDerivedPublicKeys() {
|
||||
return derivedPublicKeys;
|
||||
}
|
||||
|
||||
this.derivedPublicKeys.put(publicKey, derivation);
|
||||
}
|
||||
|
||||
public void addProprietary(String key, String data) {
|
||||
proprietary.put(key, data);
|
||||
}
|
||||
|
||||
private void testIfNull(Object obj) {
|
||||
if(obj != null) {
|
||||
throw new IllegalStateException("Duplicate keys in scope");
|
||||
}
|
||||
public Map<String, String> getProprietary() {
|
||||
return proprietary;
|
||||
}
|
||||
}
|
||||
|
|
151
src/test/java/com/craigraw/drongo/psbt/PSBTTest.java
Normal file
151
src/test/java/com/craigraw/drongo/psbt/PSBTTest.java
Normal file
|
@ -0,0 +1,151 @@
|
|||
package com.craigraw.drongo.psbt;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class PSBTTest {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void invalidNotPSBT() {
|
||||
String psbt = "AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA==";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void missingOutputs() {
|
||||
String psbt = "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void unsignedTxWithScriptSig() {
|
||||
String psbt = "cHNidP8BAP0KAQIAAAACqwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QAAAAAakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpL+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAABASAA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHhwEEFgAUhdE1N/LiZUBaNNuvqePdoB+4IwgAAAA=";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void noTransactionOnlyInputs() {
|
||||
String psbt = "cHNidP8AAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void duplicateInputKeys() {
|
||||
String psbt = "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQA/AgAAAAH//////////////////////////////////////////wAAAAAA/////wEAAAAAAAAAAANqAQAAAAAAAAAA";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidGlobalTransactionKeyType() {
|
||||
String psbt = "cHNidP8CAAFVAgAAAAEnmiMjpd+1H8RfIg+liw/BPh4zQnkqhdfjbNYzO1y8OQAAAAAA/////wGgWuoLAAAAABl2qRT/6cAGEJfMO2NvLLBGD6T8Qn0rRYisAAAAAAABASCVXuoLAAAAABepFGNFIA9o0YnhrcDfHE0W6o8UwNvrhyICA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GRjBDAiAEJLWO/6qmlOFVnqXJO7/UqJBkIkBVzfBwtncUaUQtBwIfXI6w/qZRbWC4rLM61k7eYOh4W/s6qUuZvfhhUduamgEBBCIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidInputWitnessUtxoKeyType() {
|
||||
String psbt = "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAIBACCVXuoLAAAAABepFGNFIA9o0YnhrcDfHE0W6o8UwNvrhyICA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GRjBDAiAEJLWO/6qmlOFVnqXJO7/UqJBkIkBVzfBwtncUaUQtBwIfXI6w/qZRbWC4rLM61k7eYOh4W/s6qUuZvfhhUduamgEBBCIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidInputPartialSignatureKeyType() {
|
||||
String psbt = "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIQIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYwQwIgBCS1jv+qppThVZ6lyTu/1KiQZCJAVc3wcLZ3FGlELQcCH1yOsP6mUW1guKyzOtZO3mDoeFv7OqlLmb34YVHbmpoBAQQiACB3H9GK1FlmbdSfPVZOPbxC9MhHdONgraFoFqjtSI1WgQEFR1IhA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GIQPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvVKuIgYDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYQtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIAAAA==";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidInputRedeemScriptKeyType() {
|
||||
String psbt = "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQIEACIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidInputWitnessScriptKeyType() {
|
||||
String psbt = "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoECBQBHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidInputBip32KeyType() {
|
||||
String psbt = "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriEGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb0QtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIAAAA==";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidInputNonWitnessUtxoKeyType() {
|
||||
String psbt = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAIAALsCAAAAAarXOTEBi9JfhK5AC2iEi+CdtwbqwqwYKYur7nGrZW+LAAAAAEhHMEQCIFj2/HxqM+GzFUjUgcgmwBW9MBNarULNZ3kNq2bSrSQ7AiBKHO0mBMZzW2OT5bQWkd14sA8MWUL7n3UYVvqpOBV9ugH+////AoDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSH0PIKJwEAAAAXqRQpynT4oI+BmZQoGFyXtdhS5AY/YYdlAAAAAQfaAEcwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAUgwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQEgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IcBByMiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEI2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidInputFinalScriptSigKeyType() {
|
||||
String psbt = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAACBwDaAEcwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAUgwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQEgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IcBByMiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEI2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidInputFinalScriptWitnessKeyType() {
|
||||
String psbt = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAggA2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidOutputBip32PubKey() {
|
||||
String psbt = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIQIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1PtnuylhxDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidInputSigHashKeyType() {
|
||||
String psbt = "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wCAwABAAAAAAEAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidOutputRedeemScriptKeyType() {
|
||||
String psbt = "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAgAAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void invalidOutputWitnessScriptKeyType() {
|
||||
String psbt = "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnQbVf4qHUa4A";
|
||||
PSBT.fromString(psbt);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validP2pkhOneInputEmptyOutputs() {
|
||||
String psbt = "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA";
|
||||
PSBT psbt1 = PSBT.fromString(psbt);
|
||||
Assert.assertEquals(1, psbt1.getPsbtInputs().size());
|
||||
Assert.assertEquals(2, psbt1.getPsbtOutputs().size());
|
||||
|
||||
Assert.assertEquals("af2cac1e0e33d896d9d0751d66fcb2fa54b737c7a13199281fb57e4f497bb652", psbt1.getTransaction().getTxId().toString());
|
||||
Assert.assertEquals("f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126", psbt1.getTransaction().getInputs().get(0).getOutpoint().getHash().toString());
|
||||
Assert.assertEquals(0, psbt1.getTransaction().getInputs().get(0).getOutpoint().getIndex());
|
||||
Assert.assertEquals(0, psbt1.getTransaction().getInputs().get(0).getScriptBytes().length);
|
||||
Assert.assertEquals(4294967294L, psbt1.getTransaction().getInputs().get(0).getSequenceNumber());
|
||||
|
||||
Assert.assertEquals(99999699L, psbt1.getTransaction().getOutputs().get(0).getValue());
|
||||
Assert.assertEquals("1L2tGENeoh4mSoiUZrSbs1J3jazSdJH9QS", psbt1.getTransaction().getOutputs().get(0).getScript().getToAddresses()[0].toString());
|
||||
Assert.assertEquals("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac", psbt1.getTransaction().getOutputs().get(0).getScript().getProgramAsHex());
|
||||
Assert.assertEquals("OP_DUP OP_HASH160 d0c59903c5bac2868760e90fd521a4665aa76520 OP_EQUALVERIFY OP_CHECKSIG", psbt1.getTransaction().getOutputs().get(0).getScript().toString());
|
||||
Assert.assertEquals(100000000L, psbt1.getTransaction().getOutputs().get(1).getValue());
|
||||
Assert.assertEquals("36YhUacEtcnkfhSbxwm11wDCexLGBLgJF6", psbt1.getTransaction().getOutputs().get(1).getScript().getToAddresses()[0].toString());
|
||||
Assert.assertEquals("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", psbt1.getTransaction().getOutputs().get(1).getScript().getProgramAsHex());
|
||||
Assert.assertEquals("OP_HASH160 3545e6e33b832c47050f24d3eeb93c9c03948bc7 OP_EQUAL", psbt1.getTransaction().getOutputs().get(1).getScript().toString());
|
||||
|
||||
Assert.assertEquals("f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126", psbt1.getPsbtInputs().get(0).getNonWitnessUtxo().getTxId().toString());
|
||||
Assert.assertEquals(421, psbt1.getPsbtInputs().get(0).getNonWitnessUtxo().getMessageSize());
|
||||
Assert.assertEquals("e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389", psbt1.getPsbtInputs().get(0).getNonWitnessUtxo().getInputs().get(0).getOutpoint().getHash().toString());
|
||||
Assert.assertEquals("160014be18d152a9b012039daf3da7de4f53349eecb985", psbt1.getPsbtInputs().get(0).getNonWitnessUtxo().getInputs().get(0).getScript().getProgramAsHex());
|
||||
Assert.assertEquals("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01 03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105", psbt1.getPsbtInputs().get(0).getNonWitnessUtxo().getInputs().get(0).getWitness().toString());
|
||||
Assert.assertEquals("b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886", psbt1.getPsbtInputs().get(0).getNonWitnessUtxo().getInputs().get(1).getOutpoint().getHash().toString());
|
||||
Assert.assertEquals(200000000L, psbt1.getPsbtInputs().get(0).getNonWitnessUtxo().getOutputs().get(0).getValue());
|
||||
Assert.assertEquals("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac", psbt1.getPsbtInputs().get(0).getNonWitnessUtxo().getOutputs().get(0).getScript().getProgramAsHex());
|
||||
Assert.assertEquals(190303501938L, psbt1.getPsbtInputs().get(0).getNonWitnessUtxo().getOutputs().get(1).getValue());
|
||||
Assert.assertEquals("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587", psbt1.getPsbtInputs().get(0).getNonWitnessUtxo().getOutputs().get(1).getScript().getProgramAsHex());
|
||||
|
||||
Assert.assertEquals(0, psbt1.getPsbtOutputs().get(0).getDerivedPublicKeys().size());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue