mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +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.Utils;
|
||||||
import com.craigraw.drongo.address.*;
|
import com.craigraw.drongo.address.*;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
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
|
* Returns true if this script has the required form to contain a destination address
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package com.craigraw.drongo.protocol;
|
package com.craigraw.drongo.protocol;
|
||||||
|
|
||||||
import com.craigraw.drongo.Utils;
|
import com.craigraw.drongo.Utils;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class TransactionWitness {
|
public class TransactionWitness {
|
||||||
|
@ -35,4 +37,42 @@ public class TransactionWitness {
|
||||||
stream.write(push);
|
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.ExtendedPublicKey;
|
||||||
import com.craigraw.drongo.KeyDerivation;
|
import com.craigraw.drongo.KeyDerivation;
|
||||||
import com.craigraw.drongo.Utils;
|
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 com.craigraw.drongo.protocol.*;
|
||||||
import org.bouncycastle.util.encoders.Base64;
|
import org.bouncycastle.util.encoders.Base64;
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
@ -17,29 +14,14 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static com.craigraw.drongo.psbt.PSBTEntry.parseKeyDerivation;
|
||||||
|
|
||||||
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_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 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";
|
public static final String PSBT_MAGIC = "70736274";
|
||||||
|
|
||||||
private static final int STATE_GLOBALS = 1;
|
private static final int STATE_GLOBALS = 1;
|
||||||
|
@ -51,11 +33,8 @@ public class PSBT {
|
||||||
|
|
||||||
private int inputs = 0;
|
private int inputs = 0;
|
||||||
private int outputs = 0;
|
private int outputs = 0;
|
||||||
private boolean parseOK = false;
|
|
||||||
|
|
||||||
private String strPSBT = null;
|
private byte[] psbtBytes;
|
||||||
private byte[] psbtBytes = null;
|
|
||||||
private ByteBuffer psbtByteBuffer = null;
|
|
||||||
|
|
||||||
private Transaction transaction = null;
|
private Transaction transaction = null;
|
||||||
private Integer version = null;
|
private Integer version = null;
|
||||||
|
@ -67,270 +46,120 @@ public class PSBT {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(PSBT.class);
|
private static final Logger log = LoggerFactory.getLogger(PSBT.class);
|
||||||
|
|
||||||
public PSBT(String strPSBT) throws Exception {
|
public PSBT(byte[] psbt) {
|
||||||
if (!isPSBT(strPSBT)) {
|
this.psbtBytes = psbt;
|
||||||
log.debug("Provided string is not a PSBT");
|
parse();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Utils.isBase64(strPSBT) && !Utils.isHex(strPSBT)) {
|
private void parse() {
|
||||||
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 {
|
|
||||||
int seenInputs = 0;
|
int seenInputs = 0;
|
||||||
int seenOutputs = 0;
|
int seenOutputs = 0;
|
||||||
|
|
||||||
psbtBytes = Hex.decode(strPSBT);
|
ByteBuffer psbtByteBuffer = ByteBuffer.wrap(psbtBytes);
|
||||||
psbtByteBuffer = ByteBuffer.wrap(psbtBytes);
|
|
||||||
|
|
||||||
log.debug("--- ***** START ***** ---");
|
|
||||||
log.debug("--- PSBT length:" + psbtBytes.length + "---");
|
|
||||||
log.debug("--- parsing header ---");
|
|
||||||
|
|
||||||
byte[] magicBuf = new byte[4];
|
byte[] magicBuf = new byte[4];
|
||||||
psbtByteBuffer.get(magicBuf);
|
psbtByteBuffer.get(magicBuf);
|
||||||
if (!PSBT.PSBT_MAGIC.equalsIgnoreCase(Hex.toHexString(magicBuf))) {
|
if (!PSBT_MAGIC.equalsIgnoreCase(Hex.toHexString(magicBuf))) {
|
||||||
throw new Exception("Invalid magic value");
|
throw new IllegalStateException("PSBT has invalid magic value");
|
||||||
}
|
}
|
||||||
|
|
||||||
byte sep = psbtByteBuffer.get();
|
byte sep = psbtByteBuffer.get();
|
||||||
if (sep != (byte) 0xff) {
|
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;
|
int currentState = STATE_GLOBALS;
|
||||||
PSBTInput currentInput = new PSBTInput();
|
List<PSBTEntry> globalEntries = new ArrayList<>();
|
||||||
PSBTOutput currentOutput = new PSBTOutput();
|
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()) {
|
while (psbtByteBuffer.hasRemaining()) {
|
||||||
if (currentState == STATE_GLOBALS) {
|
PSBTEntry entry = parseEntry(psbtByteBuffer);
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(entry.getKey() == null) { // length == 0
|
if(entry.getKey() == null) { // length == 0
|
||||||
switch (currentState) {
|
switch (currentState) {
|
||||||
case STATE_GLOBALS:
|
case STATE_GLOBALS:
|
||||||
currentState = STATE_INPUTS;
|
currentState = STATE_INPUTS;
|
||||||
|
parseGlobalEntries(globalEntries);
|
||||||
break;
|
break;
|
||||||
case STATE_INPUTS:
|
case STATE_INPUTS:
|
||||||
psbtInputs.add(currentInput);
|
inputEntryLists.add(inputEntries);
|
||||||
currentInput = new PSBTInput();
|
inputEntries = new ArrayList<>();
|
||||||
|
|
||||||
seenInputs++;
|
seenInputs++;
|
||||||
if (seenInputs == inputs) {
|
if (seenInputs == inputs) {
|
||||||
currentState = STATE_OUTPUTS;
|
currentState = STATE_OUTPUTS;
|
||||||
|
parseInputEntries(inputEntryLists);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STATE_OUTPUTS:
|
case STATE_OUTPUTS:
|
||||||
psbtOutputs.add(currentOutput);
|
outputEntryLists.add(outputEntries);
|
||||||
currentOutput = new PSBTOutput();
|
outputEntries = new ArrayList<>();
|
||||||
|
|
||||||
seenOutputs++;
|
seenOutputs++;
|
||||||
if (seenOutputs == outputs) {
|
if (seenOutputs == outputs) {
|
||||||
currentState = STATE_END;
|
currentState = STATE_END;
|
||||||
|
parseOutputEntries(outputEntryLists);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STATE_END:
|
case STATE_END:
|
||||||
parseOK = true;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log.debug("PSBT read is in unknown state");
|
throw new IllegalStateException("PSBT read is in unknown state");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else if (currentState == STATE_GLOBALS) {
|
} else if (currentState == STATE_GLOBALS) {
|
||||||
switch (entry.getKeyType()[0]) {
|
globalEntries.add(entry);
|
||||||
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;
|
|
||||||
}
|
|
||||||
} else if (currentState == STATE_INPUTS) {
|
} else if (currentState == STATE_INPUTS) {
|
||||||
switch (entry.getKeyType()[0]) {
|
inputEntries.add(entry);
|
||||||
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;
|
|
||||||
}
|
|
||||||
} else if (currentState == STATE_OUTPUTS) {
|
} else if (currentState == STATE_OUTPUTS) {
|
||||||
switch (entry.getKeyType()[0]) {
|
outputEntries.add(entry);
|
||||||
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;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.debug("PSBT structure invalid");
|
throw new IllegalStateException("PSBT structure invalid");
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentState == STATE_END) {
|
|
||||||
log.debug("--- ***** END ***** ---");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
PSBTEntry entry = new PSBTEntry();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int keyLen = PSBT.readCompactInt(psbtByteBuffer);
|
int keyLen = PSBT.readCompactInt(psbtByteBuffer);
|
||||||
log.debug("PSBT entry key length: " + keyLen);
|
|
||||||
|
|
||||||
if (keyLen == 0x00) {
|
if (keyLen == 0x00) {
|
||||||
log.debug("PSBT entry separator 0x00");
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] key = new byte[keyLen];
|
byte[] key = new byte[keyLen];
|
||||||
psbtByteBuffer.get(key);
|
psbtByteBuffer.get(key);
|
||||||
log.debug("PSBT entry key: " + Hex.toHexString(key));
|
|
||||||
|
|
||||||
byte[] keyType = new byte[1];
|
byte keyType = key[0];
|
||||||
keyType[0] = key[0];
|
|
||||||
log.debug("PSBT entry key type: " + Hex.toHexString(keyType));
|
|
||||||
|
|
||||||
byte[] keyData = null;
|
byte[] keyData = null;
|
||||||
if (key.length > 1) {
|
if (key.length > 1) {
|
||||||
keyData = new byte[key.length - 1];
|
keyData = new byte[key.length - 1];
|
||||||
System.arraycopy(key, 1, keyData, 0, keyData.length);
|
System.arraycopy(key, 1, keyData, 0, keyData.length);
|
||||||
log.debug("PSBT entry key data: " + Hex.toHexString(keyData));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int dataLen = PSBT.readCompactInt(psbtByteBuffer);
|
int dataLen = PSBT.readCompactInt(psbtByteBuffer);
|
||||||
log.debug("PSBT entry data length: " + dataLen);
|
|
||||||
|
|
||||||
byte[] data = new byte[dataLen];
|
byte[] data = new byte[dataLen];
|
||||||
psbtByteBuffer.get(data);
|
psbtByteBuffer.get(data);
|
||||||
log.debug("PSBT entry data: " + Hex.toHexString(data));
|
|
||||||
|
|
||||||
entry.setKey(key);
|
entry.setKey(key);
|
||||||
entry.setKeyType(keyType);
|
entry.setKeyType(keyType);
|
||||||
|
@ -340,14 +169,13 @@ public class PSBT {
|
||||||
return entry;
|
return entry;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.debug("Error parsing PSBT entry", e);
|
throw new IllegalStateException("Error parsing PSBT entry", e);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PSBTEntry populateEntry(byte type, byte[] keydata, byte[] data) throws Exception {
|
private PSBTEntry populateEntry(byte type, byte[] keydata, byte[] data) throws Exception {
|
||||||
PSBTEntry entry = new PSBTEntry();
|
PSBTEntry entry = new PSBTEntry();
|
||||||
entry.setKeyType(new byte[]{type});
|
entry.setKeyType(type);
|
||||||
entry.setKey(new byte[]{type});
|
entry.setKey(new byte[]{type});
|
||||||
if (keydata != null) {
|
if (keydata != null) {
|
||||||
entry.setKeyData(keydata);
|
entry.setKeyData(keydata);
|
||||||
|
@ -357,6 +185,89 @@ public class PSBT {
|
||||||
return entry;
|
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 {
|
public byte[] serialize() throws IOException {
|
||||||
ByteArrayOutputStream transactionbaos = new ByteArrayOutputStream();
|
ByteArrayOutputStream transactionbaos = new ByteArrayOutputStream();
|
||||||
transaction.bitcoinSerialize(transactionbaos);
|
transaction.bitcoinSerialize(transactionbaos);
|
||||||
|
@ -365,7 +276,7 @@ public class PSBT {
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
// magic
|
// 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
|
// separator
|
||||||
baos.write((byte) 0xff);
|
baos.write((byte) 0xff);
|
||||||
|
|
||||||
|
@ -412,8 +323,6 @@ public class PSBT {
|
||||||
baos.write((byte) 0x00);
|
baos.write((byte) 0x00);
|
||||||
|
|
||||||
psbtBytes = baos.toByteArray();
|
psbtBytes = baos.toByteArray();
|
||||||
strPSBT = Hex.toHexString(psbtBytes);
|
|
||||||
log.debug("Wrote PSBT: " + strPSBT);
|
|
||||||
|
|
||||||
return psbtBytes;
|
return psbtBytes;
|
||||||
}
|
}
|
||||||
|
@ -430,20 +339,10 @@ public class PSBT {
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTransaction(Transaction transaction) {
|
|
||||||
testIfNull(this.transaction);
|
|
||||||
this.transaction = transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getVersion() {
|
public Integer getVersion() {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVersion(Integer version) {
|
|
||||||
testIfNull(this.version);
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyDerivation getKeyDerivation(ExtendedPublicKey publicKey) {
|
public KeyDerivation getKeyDerivation(ExtendedPublicKey publicKey) {
|
||||||
return extendedPublicKeys.get(publicKey);
|
return extendedPublicKeys.get(publicKey);
|
||||||
}
|
}
|
||||||
|
@ -452,24 +351,6 @@ public class PSBT {
|
||||||
return new ArrayList<ExtendedPublicKey>(extendedPublicKeys.keySet());
|
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() {
|
public String toString() {
|
||||||
try {
|
try {
|
||||||
return Hex.toHexString(serialize());
|
return Hex.toHexString(serialize());
|
||||||
|
@ -558,41 +439,6 @@ public class PSBT {
|
||||||
return ret;
|
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) {
|
public static byte[] writeBIP32Derivation(byte[] fingerprint, int purpose, int type, int account, int chain, int index) {
|
||||||
// fingerprint and integer values to BIP32 derivation buffer
|
// fingerprint and integer values to BIP32 derivation buffer
|
||||||
byte[] bip32buf = new byte[24];
|
byte[] bip32buf = new byte[24];
|
||||||
|
@ -634,17 +480,30 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isPSBT(String s) {
|
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;
|
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;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
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 {
|
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;
|
PSBT psbt = null;
|
||||||
String filename = "default.psbt";
|
String filename = "default.psbt";
|
||||||
|
@ -656,7 +515,7 @@ public class PSBT {
|
||||||
stream.close();
|
stream.close();
|
||||||
psbt = new PSBT(psbtBytes);
|
psbt = new PSBT(psbtBytes);
|
||||||
} else {
|
} else {
|
||||||
psbt = new PSBT(psbtBase64);
|
psbt = PSBT.fromString(psbtBase64);
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(psbt);
|
System.out.println(psbt);
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
package com.craigraw.drongo.psbt;
|
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 {
|
public class PSBTEntry {
|
||||||
private byte[] key = null;
|
private byte[] key = null;
|
||||||
private byte[] keyType = null;
|
private byte keyType;
|
||||||
private byte[] keyData = null;
|
private byte[] keyData = null;
|
||||||
private byte[] data = null;
|
private byte[] data = null;
|
||||||
|
|
||||||
|
@ -14,11 +23,11 @@ public class PSBTEntry {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getKeyType() {
|
public byte getKeyType() {
|
||||||
return keyType;
|
return keyType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeyType(byte[] keyType) {
|
public void setKeyType(byte keyType) {
|
||||||
this.keyType = keyType;
|
this.keyType = keyType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,4 +46,57 @@ public class PSBTEntry {
|
||||||
public void setData(byte[] data) {
|
public void setData(byte[] data) {
|
||||||
this.data = 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;
|
package com.craigraw.drongo.psbt;
|
||||||
|
|
||||||
import com.craigraw.drongo.KeyDerivation;
|
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.crypto.LazyECPoint;
|
||||||
import com.craigraw.drongo.protocol.Script;
|
import com.craigraw.drongo.protocol.Script;
|
||||||
import com.craigraw.drongo.protocol.Transaction;
|
import com.craigraw.drongo.protocol.Transaction;
|
||||||
|
import com.craigraw.drongo.protocol.TransactionInput;
|
||||||
import com.craigraw.drongo.protocol.TransactionOutput;
|
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.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.craigraw.drongo.psbt.PSBTEntry.parseKeyDerivation;
|
||||||
|
|
||||||
public class PSBTInput {
|
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 Transaction nonWitnessUtxo;
|
||||||
private TransactionOutput witnessUtxo;
|
private TransactionOutput witnessUtxo;
|
||||||
private Map<LazyECPoint, byte[]> partialSignatures = new LinkedHashMap<>();
|
private Map<LazyECPoint, byte[]> partialSignatures = new LinkedHashMap<>();
|
||||||
|
@ -22,109 +45,138 @@ public class PSBTInput {
|
||||||
private String porCommitment;
|
private String porCommitment;
|
||||||
private Map<String, String> proprietary = new LinkedHashMap<>();
|
private Map<String, String> proprietary = new LinkedHashMap<>();
|
||||||
|
|
||||||
public Transaction getNonWitnessUtxo() {
|
private static final Logger log = LoggerFactory.getLogger(PSBTInput.class);
|
||||||
return nonWitnessUtxo;
|
|
||||||
|
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) {
|
public Transaction getNonWitnessUtxo() {
|
||||||
testIfNull(this.nonWitnessUtxo);
|
return nonWitnessUtxo;
|
||||||
this.nonWitnessUtxo = nonWitnessUtxo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TransactionOutput getWitnessUtxo() {
|
public TransactionOutput getWitnessUtxo() {
|
||||||
return witnessUtxo;
|
return witnessUtxo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWitnessUtxo(TransactionOutput witnessUtxo) {
|
|
||||||
testIfNull(this.witnessUtxo);
|
|
||||||
this.witnessUtxo = witnessUtxo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPartialSignature(LazyECPoint publicKey) {
|
public byte[] getPartialSignature(LazyECPoint publicKey) {
|
||||||
return partialSignatures.get(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() {
|
public Transaction.SigHash getSigHash() {
|
||||||
return sigHash;
|
return sigHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSigHash(Transaction.SigHash sigHash) {
|
|
||||||
testIfNull(this.sigHash);
|
|
||||||
this.sigHash = sigHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Script getRedeemScript() {
|
public Script getRedeemScript() {
|
||||||
return redeemScript;
|
return redeemScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRedeemScript(Script redeemScript) {
|
|
||||||
testIfNull(this.redeemScript);
|
|
||||||
this.redeemScript = redeemScript;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Script getWitnessScript() {
|
public Script getWitnessScript() {
|
||||||
return witnessScript;
|
return witnessScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWitnessScript(Script witnessScript) {
|
|
||||||
testIfNull(this.witnessScript);
|
|
||||||
this.witnessScript = witnessScript;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyDerivation getKeyDerivation(LazyECPoint publicKey) {
|
public KeyDerivation getKeyDerivation(LazyECPoint publicKey) {
|
||||||
return derivedPublicKeys.get(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() {
|
public Script getFinalScriptSig() {
|
||||||
return finalScriptSig;
|
return finalScriptSig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFinalScriptSig(Script finalScriptSig) {
|
|
||||||
testIfNull(this.finalScriptSig);
|
|
||||||
this.finalScriptSig = finalScriptSig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Script getFinalScriptWitness() {
|
public Script getFinalScriptWitness() {
|
||||||
return finalScriptWitness;
|
return finalScriptWitness;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFinalScriptWitness(Script finalScriptWitness) {
|
|
||||||
testIfNull(this.finalScriptWitness);
|
|
||||||
this.finalScriptWitness = finalScriptWitness;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPorCommitment() {
|
public String getPorCommitment() {
|
||||||
return porCommitment;
|
return porCommitment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPorCommitment(String porCommitment) {
|
public Map<LazyECPoint, byte[]> getPartialSignatures() {
|
||||||
testIfNull(this.porCommitment);
|
return partialSignatures;
|
||||||
this.porCommitment = porCommitment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addProprietary(String key, String data) {
|
public Map<LazyECPoint, KeyDerivation> getDerivedPublicKeys() {
|
||||||
proprietary.put(key, data);
|
return derivedPublicKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testIfNull(Object obj) {
|
public Map<String, String> getProprietary() {
|
||||||
if(obj != null) {
|
return proprietary;
|
||||||
throw new IllegalStateException("Duplicate keys in scope");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +1,79 @@
|
||||||
package com.craigraw.drongo.psbt;
|
package com.craigraw.drongo.psbt;
|
||||||
|
|
||||||
import com.craigraw.drongo.KeyDerivation;
|
import com.craigraw.drongo.KeyDerivation;
|
||||||
|
import com.craigraw.drongo.crypto.ECKey;
|
||||||
import com.craigraw.drongo.crypto.LazyECPoint;
|
import com.craigraw.drongo.crypto.LazyECPoint;
|
||||||
import com.craigraw.drongo.protocol.Script;
|
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.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class PSBTOutput {
|
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 redeemScript;
|
||||||
private Script witnessScript;
|
private Script witnessScript;
|
||||||
private Map<LazyECPoint, KeyDerivation> derivedPublicKeys = new LinkedHashMap<>();
|
private Map<LazyECPoint, KeyDerivation> derivedPublicKeys = new LinkedHashMap<>();
|
||||||
private Map<String, String> proprietary = new LinkedHashMap<>();
|
private Map<String, String> proprietary = new LinkedHashMap<>();
|
||||||
|
|
||||||
public Script getRedeemScript() {
|
private static final Logger log = LoggerFactory.getLogger(PSBTOutput.class);
|
||||||
return redeemScript;
|
|
||||||
|
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) {
|
public Script getRedeemScript() {
|
||||||
testIfNull(this.redeemScript);
|
return redeemScript;
|
||||||
this.redeemScript = redeemScript;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Script getWitnessScript() {
|
public Script getWitnessScript() {
|
||||||
return witnessScript;
|
return witnessScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWitnessScript(Script witnessScript) {
|
|
||||||
testIfNull(this.witnessScript);
|
|
||||||
this.witnessScript = witnessScript;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyDerivation getKeyDerivation(LazyECPoint publicKey) {
|
public KeyDerivation getKeyDerivation(LazyECPoint publicKey) {
|
||||||
return derivedPublicKeys.get(publicKey);
|
return derivedPublicKeys.get(publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDerivedPublicKey(LazyECPoint publicKey, KeyDerivation derivation) {
|
public Map<LazyECPoint, KeyDerivation> getDerivedPublicKeys() {
|
||||||
if(derivedPublicKeys.containsKey(publicKey)) {
|
return derivedPublicKeys;
|
||||||
throw new IllegalStateException("Duplicate public key in scope");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.derivedPublicKeys.put(publicKey, derivation);
|
public Map<String, String> getProprietary() {
|
||||||
}
|
return proprietary;
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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