tx input construction from script type

This commit is contained in:
Craig Raw 2020-07-03 11:00:27 +02:00
parent c4dd1cb9dd
commit 3ee7cd11eb
19 changed files with 890 additions and 41 deletions

View file

@ -2,6 +2,7 @@ package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.protocol.Base58; import com.sparrowwallet.drongo.protocol.Base58;
import com.sparrowwallet.drongo.protocol.Bech32; import com.sparrowwallet.drongo.protocol.Bech32;
import com.sparrowwallet.drongo.protocol.Script;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
import java.util.Arrays; import java.util.Arrays;
@ -31,6 +32,8 @@ public abstract class Address {
public abstract ScriptType getScriptType(); public abstract ScriptType getScriptType();
public abstract Script getOutputScript();
public abstract byte[] getOutputScriptData(); public abstract byte[] getOutputScriptData();
public abstract String getOutputScriptDataType(); public abstract String getOutputScriptDataType();

View file

@ -1,11 +1,9 @@
package com.sparrowwallet.drongo.address; package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.Script;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
import java.util.ArrayList;
import java.util.List;
public class P2PKAddress extends Address { public class P2PKAddress extends Address {
private byte[] pubKey; private byte[] pubKey;
@ -22,6 +20,10 @@ public class P2PKAddress extends Address {
return ScriptType.P2PK; return ScriptType.P2PK;
} }
public Script getOutputScript() {
return getScriptType().getOutputScript(pubKey);
}
@Override @Override
public byte[] getOutputScriptData() { public byte[] getOutputScriptData() {
return pubKey; return pubKey;

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.drongo.address; package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.protocol.Script;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
public class P2PKHAddress extends Address { public class P2PKHAddress extends Address {
@ -15,6 +16,10 @@ public class P2PKHAddress extends Address {
return ScriptType.P2PKH; return ScriptType.P2PKH;
} }
public Script getOutputScript() {
return getScriptType().getOutputScript(hash);
}
@Override @Override
public byte[] getOutputScriptData() { public byte[] getOutputScriptData() {
return hash; return hash;

View file

@ -1,6 +1,7 @@
package com.sparrowwallet.drongo.address; package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.Script;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
public class P2SHAddress extends Address { public class P2SHAddress extends Address {
@ -12,10 +13,16 @@ public class P2SHAddress extends Address {
return 5; return 5;
} }
@Override
public ScriptType getScriptType() { public ScriptType getScriptType() {
return ScriptType.P2SH; return ScriptType.P2SH;
} }
@Override
public Script getOutputScript() {
return getScriptType().getOutputScript(hash);
}
@Override @Override
public byte[] getOutputScriptData() { public byte[] getOutputScriptData() {
return hash; return hash;

View file

@ -1,6 +1,7 @@
package com.sparrowwallet.drongo.address; package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.protocol.Bech32; import com.sparrowwallet.drongo.protocol.Bech32;
import com.sparrowwallet.drongo.protocol.Script;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
public class P2WPKHAddress extends Address { public class P2WPKHAddress extends Address {
@ -22,6 +23,11 @@ public class P2WPKHAddress extends Address {
return ScriptType.P2WPKH; return ScriptType.P2WPKH;
} }
@Override
public Script getOutputScript() {
return getScriptType().getOutputScript(hash);
}
@Override @Override
public byte[] getOutputScriptData() { public byte[] getOutputScriptData() {
return hash; return hash;

View file

@ -21,6 +21,11 @@ public class P2WSHAddress extends Address {
return ScriptType.P2WSH; return ScriptType.P2WSH;
} }
@Override
public Script getOutputScript() {
return getScriptType().getOutputScript(hash);
}
@Override @Override
public byte[] getOutputScriptData() { public byte[] getOutputScriptData() {
return hash; return hash;

View file

@ -4,7 +4,11 @@ public abstract class ChildMessage extends Message {
protected Message parent; protected Message parent;
public ChildMessage(byte[] rawtx, int offset) { protected ChildMessage() {
super();
}
protected ChildMessage(byte[] rawtx, int offset) {
super(rawtx, offset); super(rawtx, offset);
} }

View file

@ -23,7 +23,11 @@ public abstract class Message {
protected int length = UNKNOWN_LENGTH; protected int length = UNKNOWN_LENGTH;
public Message(byte[] payload, int offset) { protected Message() {
}
protected Message(byte[] payload, int offset) {
this.payload = payload; this.payload = payload;
this.cursor = this.offset = offset; this.cursor = this.offset = offset;

View file

@ -41,10 +41,10 @@ public class Script {
} }
private static final ScriptChunk[] STANDARD_TRANSACTION_SCRIPT_CHUNKS = { private static final ScriptChunk[] STANDARD_TRANSACTION_SCRIPT_CHUNKS = {
new ScriptChunk(ScriptOpCodes.OP_DUP, null, 0), new ScriptChunk(ScriptOpCodes.OP_DUP, null),
new ScriptChunk(ScriptOpCodes.OP_HASH160, null, 1), new ScriptChunk(ScriptOpCodes.OP_HASH160, null),
new ScriptChunk(ScriptOpCodes.OP_EQUALVERIFY, null, 23), new ScriptChunk(ScriptOpCodes.OP_EQUALVERIFY, null),
new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, null, 24), new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, null),
}; };
void parse() { void parse() {
@ -52,7 +52,6 @@ public class Script {
ByteArrayInputStream bis = new ByteArrayInputStream(program); ByteArrayInputStream bis = new ByteArrayInputStream(program);
int initialSize = bis.available(); int initialSize = bis.available();
while (bis.available() > 0) { while (bis.available() > 0) {
int startLocationInProgram = initialSize - bis.available();
int opcode = bis.read(); int opcode = bis.read();
long dataToRead = -1; long dataToRead = -1;
@ -75,7 +74,7 @@ public class Script {
ScriptChunk chunk; ScriptChunk chunk;
if (dataToRead == -1) { if (dataToRead == -1) {
chunk = new ScriptChunk(opcode, null, startLocationInProgram); chunk = new ScriptChunk(opcode, null);
} else { } else {
if (dataToRead > bis.available()) if (dataToRead > bis.available())
throw new ProtocolException("Push of data element that is larger than remaining data"); throw new ProtocolException("Push of data element that is larger than remaining data");
@ -84,7 +83,7 @@ public class Script {
throw new ProtocolException(); throw new ProtocolException();
} }
chunk = new ScriptChunk(opcode, data, startLocationInProgram); chunk = new ScriptChunk(opcode, data);
} }
// Save some memory by eliminating redundant copies of the same chunk objects. // Save some memory by eliminating redundant copies of the same chunk objects.
for (ScriptChunk c : STANDARD_TRANSACTION_SCRIPT_CHUNKS) { for (ScriptChunk c : STANDARD_TRANSACTION_SCRIPT_CHUNKS) {

View file

@ -22,16 +22,38 @@ public class ScriptChunk {
*/ */
public final byte[] data; public final byte[] data;
private int startLocationInProgram;
public ScriptChunk(int opcode, byte[] data) { public ScriptChunk(int opcode, byte[] data) {
this(opcode, data, -1);
}
public ScriptChunk(int opcode, byte[] data, int startLocationInProgram) {
this.opcode = opcode; this.opcode = opcode;
this.data = data; this.data = data;
this.startLocationInProgram = startLocationInProgram; }
public static ScriptChunk fromOpcode(int opcode) {
return new ScriptChunk(opcode, null);
}
public static ScriptChunk fromData(byte[] data) {
byte[] copy = Arrays.copyOf(data, data.length);
int opcode;
if (data.length == 0) {
opcode = OP_0;
} else if (data.length == 1) {
byte b = data[0];
if (b >= 1 && b <= 16) {
opcode = Script.encodeToOpN(b);
} else {
opcode = 1;
}
} else if (data.length < OP_PUSHDATA1) {
opcode = data.length;
} else if (data.length < 256) {
opcode = OP_PUSHDATA1;
} else if (data.length < 65536) {
opcode = OP_PUSHDATA2;
} else {
opcode = OP_PUSHDATA4;
}
return new ScriptChunk(opcode, copy);
} }
public boolean equalsOpCode(int opcode) { public boolean equalsOpCode(int opcode) {

View file

@ -92,6 +92,33 @@ public enum ScriptType {
return ECKey.fromPublicOnly(script.chunks.get(0).data); return ECKey.fromPublicOnly(script.chunks.get(0).data);
} }
@Override
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
if(!isScriptType(scriptPubKey)) {
throw new ProtocolException("Provided scriptPubKey is not a " + getName() + " script");
}
byte[] signatureBytes = signature.encodeToBitcoin();
ScriptChunk signatureChunk = ScriptChunk.fromData(signatureBytes);
return new Script(List.of(signatureChunk));
}
@Override
public TransactionInput addSpendingInput(Transaction transaction, TransactionOutput prevOutput, ECKey pubKey, TransactionSignature signature) {
Script scriptSig = getScriptSig(prevOutput.getScript(), pubKey, signature);
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
}
@Override
public Script getMultisigScriptSig(Script scriptPubKey, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override
public TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override @Override
public List<PolicyType> getAllowedPolicyTypes() { public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE); return List.of(SINGLE);
@ -171,6 +198,35 @@ public enum ScriptType {
return script.chunks.get(2).data; return script.chunks.get(2).data;
} }
@Override
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
if(!isScriptType(scriptPubKey)) {
throw new ProtocolException("Provided scriptPubKey is not a " + getName() + " script");
}
byte[] signatureBytes = signature.encodeToBitcoin();
ScriptChunk signatureChunk = ScriptChunk.fromData(signatureBytes);
byte[] pubKeyBytes = pubKey.getPubKey();
ScriptChunk pubKeyChunk = ScriptChunk.fromData(pubKeyBytes);
return new Script(List.of(signatureChunk, pubKeyChunk));
}
@Override
public TransactionInput addSpendingInput(Transaction transaction, TransactionOutput prevOutput, ECKey pubKey, TransactionSignature signature) {
Script scriptSig = getScriptSig(prevOutput.getScript(), pubKey, signature);
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
}
@Override
public Script getMultisigScriptSig(Script scriptPubKey, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override
public TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override @Override
public List<PolicyType> getAllowedPolicyTypes() { public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE); return List.of(SINGLE);
@ -218,6 +274,10 @@ public enum ScriptType {
@Override @Override
public Script getOutputScript(int threshold, List<ECKey> pubKeys) { public Script getOutputScript(int threshold, List<ECKey> pubKeys) {
if(threshold > pubKeys.size()) {
throw new ProtocolException("Threshold of " + threshold + " is greater than number of pubKeys provided (" + pubKeys.size() + ")");
}
List<byte[]> pubKeyBytes = new ArrayList<>(); List<byte[]> pubKeyBytes = new ArrayList<>();
for(ECKey key : pubKeys) { for(ECKey key : pubKeys) {
pubKeyBytes.add(key.getPubKey()); pubKeyBytes.add(key.getPubKey());
@ -310,6 +370,42 @@ public enum ScriptType {
return decodeFromOpN(script.chunks.get(0).opcode); return decodeFromOpN(script.chunks.get(0).opcode);
} }
@Override
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
throw new ProtocolException(getName() + " is a multisig script type");
}
@Override
public TransactionInput addSpendingInput(Transaction transaction, TransactionOutput prevOutput, ECKey pubKey, TransactionSignature signature) {
throw new ProtocolException(getName() + " is a multisig script type");
}
@Override
public Script getMultisigScriptSig(Script scriptPubKey, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
if(!isScriptType(scriptPubKey)) {
throw new ProtocolException("Provided scriptPubKey is not a " + getName() + " script");
}
if(threshold != signatures.size()) {
throw new ProtocolException("Only " + signatures.size() + " signatures provided to meet a multisig threshold of " + threshold);
}
List<ScriptChunk> chunks = new ArrayList<>(signatures.size() + 1);
ScriptChunk opZero = ScriptChunk.fromOpcode(OP_0);
chunks.add(opZero);
for(TransactionSignature signature : signatures) {
byte[] signatureBytes = signature.encodeToBitcoin();
chunks.add(ScriptChunk.fromData(signatureBytes));
}
return new Script(chunks);
}
@Override
public TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
Script scriptSig = getMultisigScriptSig(prevOutput.getScript(), threshold, pubKeys, signatures);
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
}
@Override @Override
public List<PolicyType> getAllowedPolicyTypes() { public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI); return List.of(MULTI);
@ -395,6 +491,41 @@ public enum ScriptType {
return script.chunks.get(1).data; return script.chunks.get(1).data;
} }
@Override
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
throw new ProtocolException("Only multisig scriptSigs supported for " + getName() + " scriptPubKeys");
}
@Override
public TransactionInput addSpendingInput(Transaction transaction, TransactionOutput prevOutput, ECKey pubKey, TransactionSignature signature) {
throw new ProtocolException("Only multisig scriptSigs supported for " + getName() + " scriptPubKeys");
}
@Override
public Script getMultisigScriptSig(Script scriptPubKey, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
if(!isScriptType(scriptPubKey)) {
throw new ProtocolException("Provided scriptPubKey is not a " + getName() + " script");
}
Script redeemScript = MULTISIG.getOutputScript(threshold, pubKeys);
if(!scriptPubKey.equals(getOutputScript(redeemScript))) {
throw new ProtocolException("P2SH scriptPubKey hash does not match constructed redeem script hash");
}
Script multisigScript = MULTISIG.getMultisigScriptSig(redeemScript, threshold, pubKeys, signatures);
List<ScriptChunk> chunks = new ArrayList<>(multisigScript.getChunks());
ScriptChunk redeemScriptChunk = ScriptChunk.fromData(redeemScript.getProgram());
chunks.add(redeemScriptChunk);
return new Script(chunks);
}
@Override
public TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
Script scriptSig = getMultisigScriptSig(prevOutput.getScript(), threshold, pubKeys, signatures);
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig);
}
@Override @Override
public List<PolicyType> getAllowedPolicyTypes() { public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI); return List.of(MULTI);
@ -461,6 +592,38 @@ public enum ScriptType {
return P2SH.getHashFromScript(script); return P2SH.getHashFromScript(script);
} }
@Override
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
if(!isScriptType(scriptPubKey)) {
throw new ProtocolException("Provided scriptPubKey is not a " + getName() + " script");
}
Script redeemScript = P2WPKH.getOutputScript(pubKey);
if(!scriptPubKey.equals(P2SH.getOutputScript(redeemScript))) {
throw new ProtocolException(getName() + " scriptPubKey hash does not match constructed redeem script hash");
}
ScriptChunk redeemScriptChunk = ScriptChunk.fromData(redeemScript.getProgram());
return new Script(List.of(redeemScriptChunk));
}
@Override
public TransactionInput addSpendingInput(Transaction transaction, TransactionOutput prevOutput, ECKey pubKey, TransactionSignature signature) {
Script scriptSig = getScriptSig(prevOutput.getScript(), pubKey, signature);
TransactionWitness witness = new TransactionWitness(transaction, pubKey, signature);
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
}
@Override
public Script getMultisigScriptSig(Script scriptPubKey, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override
public TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override @Override
public List<PolicyType> getAllowedPolicyTypes() { public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE); return List.of(SINGLE);
@ -523,6 +686,40 @@ public enum ScriptType {
return P2SH.getHashFromScript(script); return P2SH.getHashFromScript(script);
} }
@Override
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
throw new ProtocolException("Only multisig scriptSigs supported for " + getName() + " scriptPubKeys");
}
@Override
public TransactionInput addSpendingInput(Transaction transaction, TransactionOutput prevOutput, ECKey pubKey, TransactionSignature signature) {
throw new ProtocolException("Only multisig scriptSigs supported for " + getName() + " scriptPubKeys");
}
@Override
public Script getMultisigScriptSig(Script scriptPubKey, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
if(!isScriptType(scriptPubKey)) {
throw new ProtocolException("Provided scriptPubKey is not a " + getName() + " script");
}
Script witnessScript = MULTISIG.getOutputScript(threshold, pubKeys);
Script redeemScript = P2WSH.getOutputScript(witnessScript);
if(!scriptPubKey.equals(P2SH.getOutputScript(redeemScript))) {
throw new ProtocolException("P2SH scriptPubKey hash does not match constructed redeem script hash");
}
ScriptChunk redeemScriptChunk = ScriptChunk.fromData(redeemScript.getProgram());
return new Script(List.of(redeemScriptChunk));
}
@Override
public TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
Script scriptSig = getMultisigScriptSig(prevOutput.getScript(), threshold, pubKeys, signatures);
Script witnessScript = MULTISIG.getOutputScript(threshold, pubKeys);
TransactionWitness witness = new TransactionWitness(transaction, signatures, witnessScript);
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
}
@Override @Override
public List<PolicyType> getAllowedPolicyTypes() { public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI, CUSTOM); return List.of(MULTI, CUSTOM);
@ -593,6 +790,36 @@ public enum ScriptType {
return script.chunks.get(1).data; return script.chunks.get(1).data;
} }
@Override
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
if(!isScriptType(scriptPubKey)) {
throw new ProtocolException("Provided scriptPubKey is not a " + getName() + " script");
}
if(!scriptPubKey.equals(getOutputScript(pubKey))) {
throw new ProtocolException("P2WPKH scriptPubKey hash does not match constructed pubkey script hash");
}
return new Script(new byte[0]);
}
@Override
public TransactionInput addSpendingInput(Transaction transaction, TransactionOutput prevOutput, ECKey pubKey, TransactionSignature signature) {
Script scriptSig = getScriptSig(prevOutput.getScript(), pubKey, signature);
TransactionWitness witness = new TransactionWitness(transaction, pubKey, signature);
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
}
@Override
public Script getMultisigScriptSig(Script scriptPubKey, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override
public TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
throw new ProtocolException(getName() + " is not a multisig script type");
}
@Override @Override
public List<PolicyType> getAllowedPolicyTypes() { public List<PolicyType> getAllowedPolicyTypes() {
return List.of(SINGLE); return List.of(SINGLE);
@ -667,6 +894,38 @@ public enum ScriptType {
return script.chunks.get(1).data; return script.chunks.get(1).data;
} }
@Override
public Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature) {
throw new ProtocolException("Only multisig scriptSigs supported for " + getName() + " scriptPubKeys");
}
@Override
public TransactionInput addSpendingInput(Transaction transaction, TransactionOutput prevOutput, ECKey pubKey, TransactionSignature signature) {
throw new ProtocolException("Only multisig scriptSigs supported for " + getName() + " scriptPubKeys");
}
@Override
public Script getMultisigScriptSig(Script scriptPubKey, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
if(!isScriptType(scriptPubKey)) {
throw new ProtocolException("Provided scriptPubKey is not a " + getName() + " script");
}
Script witnessScript = MULTISIG.getOutputScript(threshold, pubKeys);
if(!scriptPubKey.equals(P2WSH.getOutputScript(witnessScript))) {
throw new ProtocolException("P2WSH scriptPubKey hash does not match constructed witness script hash");
}
return new Script(new byte[0]);
}
@Override
public TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures) {
Script scriptSig = getMultisigScriptSig(prevOutput.getScript(), threshold, pubKeys, signatures);
Script witnessScript = MULTISIG.getOutputScript(threshold, pubKeys);
TransactionWitness witness = new TransactionWitness(transaction, signatures, witnessScript);
return transaction.addInput(prevOutput.getHash(), prevOutput.getIndex(), scriptSig, witness);
}
@Override @Override
public List<PolicyType> getAllowedPolicyTypes() { public List<PolicyType> getAllowedPolicyTypes() {
return List.of(MULTI, CUSTOM); return List.of(MULTI, CUSTOM);
@ -761,12 +1020,30 @@ public enum ScriptType {
throw new ProtocolException("Script type " + this + " is not a multisig script"); throw new ProtocolException("Script type " + this + " is not a multisig script");
} }
public abstract Script getScriptSig(Script scriptPubKey, ECKey pubKey, TransactionSignature signature);
public abstract TransactionInput addSpendingInput(Transaction transaction, TransactionOutput prevOutput, ECKey pubKey, TransactionSignature signature);
public abstract Script getMultisigScriptSig(Script scriptPubKey, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures);
public abstract TransactionInput addMultisigSpendingInput(Transaction transaction, TransactionOutput prevOutput, int threshold, List<ECKey> pubKeys, List<TransactionSignature> signatures);
public static final ScriptType[] SINGLE_HASH_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH}; public static final ScriptType[] SINGLE_HASH_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH};
public static List<ScriptType> getScriptTypesForPolicyType(PolicyType policyType) { public static List<ScriptType> getScriptTypesForPolicyType(PolicyType policyType) {
return Arrays.stream(values()).filter(scriptType -> scriptType.isAllowed(policyType)).collect(Collectors.toList()); return Arrays.stream(values()).filter(scriptType -> scriptType.isAllowed(policyType)).collect(Collectors.toList());
} }
public static ScriptType getType(Script script) {
for(ScriptType type : values()) {
if(type.isScriptType(script)) {
return type;
}
}
return null;
}
@Override @Override
public String toString() { public String toString() {
return name; return name;

View file

@ -33,6 +33,13 @@ public class Transaction extends ChildMessage {
private ArrayList<TransactionInput> inputs; private ArrayList<TransactionInput> inputs;
private ArrayList<TransactionOutput> outputs; private ArrayList<TransactionOutput> outputs;
public Transaction() {
version = 1;
inputs = new ArrayList<>();
outputs = new ArrayList<>();
length = 8;
}
public Transaction(byte[] rawtx) { public Transaction(byte[] rawtx) {
super(rawtx, 0); super(rawtx, 0);
} }
@ -125,6 +132,11 @@ public class Transaction extends ChildMessage {
} }
public void setSegwitVersion(int segwitVersion) { public void setSegwitVersion(int segwitVersion) {
if(!segwit) {
adjustLength(2);
this.segwit = true;
}
this.segwitVersion = segwitVersion; this.segwitVersion = segwitVersion;
} }
@ -272,10 +284,44 @@ public class Transaction extends ChildMessage {
return Collections.unmodifiableList(inputs); return Collections.unmodifiableList(inputs);
} }
public TransactionInput addInput(Sha256Hash spendTxHash, long outputIndex, Script script) {
return addInput(new TransactionInput(this, new TransactionOutPoint(spendTxHash, outputIndex), script.getProgram()));
}
public TransactionInput addInput(Sha256Hash spendTxHash, long outputIndex, Script script, TransactionWitness witness) {
return addInput(new TransactionInput(this, new TransactionOutPoint(spendTxHash, outputIndex), script.getProgram(), witness));
}
public TransactionInput addInput(TransactionInput input) {
input.setParent(this);
inputs.add(input);
adjustLength(inputs.size(), input.length);
return input;
}
public List<TransactionOutput> getOutputs() { public List<TransactionOutput> getOutputs() {
return Collections.unmodifiableList(outputs); return Collections.unmodifiableList(outputs);
} }
public TransactionOutput addOutput(long value, Script script) {
return addOutput(new TransactionOutput(this, value, script));
}
public TransactionOutput addOutput(long value, Address address) {
return addOutput(new TransactionOutput(this, value, address.getOutputScript()));
}
public TransactionOutput addOutput(long value, ECKey pubkey) {
return addOutput(new TransactionOutput(this, value, ScriptType.P2PK.getOutputScript(pubkey)));
}
public TransactionOutput addOutput(TransactionOutput output) {
output.setParent(this);
outputs.add(output);
adjustLength(outputs.size(), output.length);
return output;
}
public void verify() throws VerificationException { public void verify() throws VerificationException {
if (inputs.size() == 0 || outputs.size() == 0) if (inputs.size() == 0 || outputs.size() == 0)
throw new VerificationException.EmptyInputsOrOutputs(); throw new VerificationException.EmptyInputsOrOutputs();

View file

@ -25,6 +25,23 @@ public class TransactionInput extends ChildMessage {
private TransactionWitness witness; private TransactionWitness witness;
public TransactionInput(Transaction transaction, TransactionOutPoint outpoint, byte[] scriptBytes) {
this(transaction, outpoint, scriptBytes, null);
}
public TransactionInput(Transaction transaction, TransactionOutPoint outpoint, byte[] scriptBytes, TransactionWitness witness) {
setParent(transaction);
this.sequence = SEQUENCE_LOCKTIME_DISABLED;
this.outpoint = outpoint;
this.outpoint.setParent(this);
this.scriptBytes = scriptBytes;
this.witness = witness;
length = 40 + (scriptBytes == null ? 1 : VarInt.sizeOf(scriptBytes.length) + scriptBytes.length);
if(witness != null) {
transaction.adjustLength(witness.getLength());
}
}
public TransactionInput(Transaction transaction, byte[] rawtx, int offset) { public TransactionInput(Transaction transaction, byte[] rawtx, int offset) {
super(rawtx, offset); super(rawtx, offset);
setParent(transaction); setParent(transaction);

View file

@ -18,6 +18,12 @@ public class TransactionOutPoint extends ChildMessage {
private Address[] addresses = new Address[0]; private Address[] addresses = new Address[0];
public TransactionOutPoint(Sha256Hash hash, long index) {
this.hash = hash;
this.index = index;
length = MESSAGE_LENGTH;
}
public TransactionOutPoint(byte[] rawtx, int offset, Message parent) { public TransactionOutPoint(byte[] rawtx, int offset, Message parent) {
super(rawtx, offset); super(rawtx, offset);
setParent(parent); setParent(parent);

View file

@ -3,7 +3,6 @@ package com.sparrowwallet.drongo.protocol;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.address.Address;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -19,27 +18,22 @@ public class TransactionOutput extends ChildMessage {
private Address[] addresses = new Address[0]; private Address[] addresses = new Address[0];
public TransactionOutput(Transaction transaction, long value, Script script) {
this(transaction, value, script.getProgram());
}
public TransactionOutput(Transaction transaction, long value, byte[] scriptBytes) {
this.value = value;
this.scriptBytes = scriptBytes;
setParent(transaction);
length = 8 + VarInt.sizeOf(scriptBytes.length) + scriptBytes.length;
}
public TransactionOutput(Transaction parent, byte[] rawtx, int offset) { public TransactionOutput(Transaction parent, byte[] rawtx, int offset) {
super(rawtx, offset); super(rawtx, offset);
setParent(parent); setParent(parent);
} }
public TransactionOutput(Transaction parent, long value, byte[] scriptBytes) {
super(new byte[0], 0);
this.value = value;
this.scriptBytes = scriptBytes;
setParent(parent);
length = 8 + VarInt.sizeOf(scriptBytes.length) + scriptBytes.length;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitcoinSerializeToStream(baos);
payload = baos.toByteArray();
} catch(IOException e) {
//ignore
}
}
protected void parse() throws ProtocolException { protected void parse() throws ProtocolException {
value = readInt64(); value = readInt64();
int scriptLen = (int) readVarInt(); int scriptLen = (int) readVarInt();
@ -78,6 +72,11 @@ public class TransactionOutput extends ChildMessage {
this.addresses = addresses; this.addresses = addresses;
} }
public Sha256Hash getHash() {
Transaction transaction = (Transaction)parent;
return transaction.getTxId();
}
public int getIndex() { public int getIndex() {
Transaction transaction = (Transaction)parent; Transaction transaction = (Transaction)parent;
return transaction.getOutputs().indexOf(this); return transaction.getOutputs().indexOf(this);

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.drongo.protocol; package com.sparrowwallet.drongo.protocol;
import com.sparrowwallet.drongo.crypto.ECKey;
import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.encoders.Hex;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -13,6 +14,30 @@ import java.util.List;
public class TransactionWitness extends ChildMessage { public class TransactionWitness extends ChildMessage {
private List<byte[]> pushes; private List<byte[]> pushes;
public TransactionWitness(Transaction transaction, ECKey pubKey, TransactionSignature signature) {
setParent(transaction);
this.pushes = new ArrayList<>();
pushes.add(signature.encodeToBitcoin());
pushes.add(pubKey.getPubKey());
}
public TransactionWitness(Transaction transaction, List<TransactionSignature> signatures, Script witnessScript) {
setParent(transaction);
this.pushes = new ArrayList<>();
if(ScriptType.MULTISIG.isScriptType(witnessScript)) {
pushes.add(new byte[] { ScriptOpCodes.OP_0 });
}
for(TransactionSignature signature : signatures) {
pushes.add(signature.encodeToBitcoin());
}
pushes.add(witnessScript.getProgram());
}
public TransactionWitness(Transaction transaction, List<byte[]> witnesses) {
setParent(transaction);
this.pushes = witnesses;
}
public TransactionWitness(Transaction parent, byte[] rawtx, int offset) { public TransactionWitness(Transaction parent, byte[] rawtx, int offset) {
super(rawtx, offset); super(rawtx, offset);
setParent(parent); setParent(parent);
@ -53,9 +78,13 @@ public class TransactionWitness extends ChildMessage {
int length = new VarInt(pushes.size()).getSizeInBytes(); int length = new VarInt(pushes.size()).getSizeInBytes();
for (int i = 0; i < pushes.size(); i++) { for (int i = 0; i < pushes.size(); i++) {
byte[] push = pushes.get(i); byte[] push = pushes.get(i);
if(push.length == 1 && push[0] == 0) {
length++;
} else {
length += new VarInt(push.length).getSizeInBytes(); length += new VarInt(push.length).getSizeInBytes();
length += push.length; length += push.length;
} }
}
return length; return length;
} }
@ -64,10 +93,14 @@ public class TransactionWitness extends ChildMessage {
stream.write(new VarInt(pushes.size()).encode()); stream.write(new VarInt(pushes.size()).encode());
for (int i = 0; i < pushes.size(); i++) { for (int i = 0; i < pushes.size(); i++) {
byte[] push = pushes.get(i); byte[] push = pushes.get(i);
if(push.length == 1 && push[0] == 0) {
stream.write(push);
} else {
stream.write(new VarInt(push.length).encode()); stream.write(new VarInt(push.length).encode());
stream.write(push); stream.write(push);
} }
} }
}
public byte[] toByteArray() { public byte[] toByteArray() {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();

View file

@ -0,0 +1,59 @@
package com.sparrowwallet.drongo.wallet;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;
public class PriorityUtxoSelector implements UtxoSelector {
private final int currentBlockHeight;
public PriorityUtxoSelector(int currentBlockHeight) {
this.currentBlockHeight = currentBlockHeight;
}
@Override
public Collection<BlockTransactionHashIndex> select(long targetValue, Collection<BlockTransactionHashIndex> candidates) {
List<BlockTransactionHashIndex> selected = new ArrayList<>();
List<BlockTransactionHashIndex> sorted = candidates.stream().filter(ref -> ref.getHeight() != 0).collect(Collectors.toList());
sort(sorted);
long total = 0;
for(BlockTransactionHashIndex reference : sorted) {
if(total > targetValue) {
break;
}
selected.add(reference);
total += reference.getValue();
}
return selected;
}
private void sort(List<BlockTransactionHashIndex> outputs) {
outputs.sort((a, b) -> {
int depthA = currentBlockHeight - a.getHeight();
int depthB = currentBlockHeight - b.getHeight();
Long valueA = a.getValue();
Long valueB = b.getValue();
BigInteger coinDepthA = BigInteger.valueOf(depthA).multiply(BigInteger.valueOf(valueA));
BigInteger coinDepthB = BigInteger.valueOf(depthB).multiply(BigInteger.valueOf(valueB));
int coinDepthCompare = coinDepthB.compareTo(coinDepthA);
if (coinDepthCompare != 0) {
return coinDepthCompare;
}
// The "coin*days" destroyed are equal, sort by value alone to get the lowest transaction size.
int coinValueCompare = valueB.compareTo(valueA);
if (coinValueCompare != 0) {
return coinValueCompare;
}
return a.compareTo(b);
});
}
}

View file

@ -205,6 +205,23 @@ public class Wallet {
} }
} }
public Map<BlockTransactionHashIndex, WalletNode> getWalletUtxos() {
Map<BlockTransactionHashIndex, WalletNode> walletUtxos = new TreeMap<>();
getWalletUtxos(walletUtxos, getNode(KeyPurpose.RECEIVE));
getWalletUtxos(walletUtxos, getNode(KeyPurpose.CHANGE));
return walletUtxos;
}
private void getWalletUtxos(Map<BlockTransactionHashIndex, WalletNode> walletUtxos, WalletNode purposeNode) {
for(WalletNode addressNode : purposeNode.getChildren()) {
for(BlockTransactionHashIndex utxo : addressNode.getUnspentTransactionOutputs()) {
walletUtxos.put(utxo, addressNode);
}
}
}
public void clearNodes() { public void clearNodes() {
purposeNodes.clear(); purposeNodes.clear();
transactions.clear(); transactions.clear();

File diff suppressed because one or more lines are too long