mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +00:00
tx input construction from script type
This commit is contained in:
parent
c4dd1cb9dd
commit
3ee7cd11eb
19 changed files with 890 additions and 41 deletions
|
@ -2,6 +2,7 @@ package com.sparrowwallet.drongo.address;
|
|||
|
||||
import com.sparrowwallet.drongo.protocol.Base58;
|
||||
import com.sparrowwallet.drongo.protocol.Bech32;
|
||||
import com.sparrowwallet.drongo.protocol.Script;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -31,6 +32,8 @@ public abstract class Address {
|
|||
|
||||
public abstract ScriptType getScriptType();
|
||||
|
||||
public abstract Script getOutputScript();
|
||||
|
||||
public abstract byte[] getOutputScriptData();
|
||||
|
||||
public abstract String getOutputScriptDataType();
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package com.sparrowwallet.drongo.address;
|
||||
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.protocol.Script;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class P2PKAddress extends Address {
|
||||
private byte[] pubKey;
|
||||
|
||||
|
@ -22,6 +20,10 @@ public class P2PKAddress extends Address {
|
|||
return ScriptType.P2PK;
|
||||
}
|
||||
|
||||
public Script getOutputScript() {
|
||||
return getScriptType().getOutputScript(pubKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getOutputScriptData() {
|
||||
return pubKey;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.sparrowwallet.drongo.address;
|
||||
|
||||
import com.sparrowwallet.drongo.protocol.Script;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
|
||||
public class P2PKHAddress extends Address {
|
||||
|
@ -15,6 +16,10 @@ public class P2PKHAddress extends Address {
|
|||
return ScriptType.P2PKH;
|
||||
}
|
||||
|
||||
public Script getOutputScript() {
|
||||
return getScriptType().getOutputScript(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getOutputScriptData() {
|
||||
return hash;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.sparrowwallet.drongo.address;
|
||||
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.protocol.Script;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
|
||||
public class P2SHAddress extends Address {
|
||||
|
@ -12,10 +13,16 @@ public class P2SHAddress extends Address {
|
|||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptType getScriptType() {
|
||||
return ScriptType.P2SH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Script getOutputScript() {
|
||||
return getScriptType().getOutputScript(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getOutputScriptData() {
|
||||
return hash;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.sparrowwallet.drongo.address;
|
||||
|
||||
import com.sparrowwallet.drongo.protocol.Bech32;
|
||||
import com.sparrowwallet.drongo.protocol.Script;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
|
||||
public class P2WPKHAddress extends Address {
|
||||
|
@ -22,6 +23,11 @@ public class P2WPKHAddress extends Address {
|
|||
return ScriptType.P2WPKH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Script getOutputScript() {
|
||||
return getScriptType().getOutputScript(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getOutputScriptData() {
|
||||
return hash;
|
||||
|
|
|
@ -21,6 +21,11 @@ public class P2WSHAddress extends Address {
|
|||
return ScriptType.P2WSH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Script getOutputScript() {
|
||||
return getScriptType().getOutputScript(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getOutputScriptData() {
|
||||
return hash;
|
||||
|
|
|
@ -4,7 +4,11 @@ public abstract class ChildMessage extends Message {
|
|||
|
||||
protected Message parent;
|
||||
|
||||
public ChildMessage(byte[] rawtx, int offset) {
|
||||
protected ChildMessage() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected ChildMessage(byte[] rawtx, int offset) {
|
||||
super(rawtx, offset);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,11 @@ public abstract class Message {
|
|||
|
||||
protected int length = UNKNOWN_LENGTH;
|
||||
|
||||
public Message(byte[] payload, int offset) {
|
||||
protected Message() {
|
||||
|
||||
}
|
||||
|
||||
protected Message(byte[] payload, int offset) {
|
||||
this.payload = payload;
|
||||
this.cursor = this.offset = offset;
|
||||
|
||||
|
|
|
@ -41,10 +41,10 @@ public class Script {
|
|||
}
|
||||
|
||||
private static final ScriptChunk[] STANDARD_TRANSACTION_SCRIPT_CHUNKS = {
|
||||
new ScriptChunk(ScriptOpCodes.OP_DUP, null, 0),
|
||||
new ScriptChunk(ScriptOpCodes.OP_HASH160, null, 1),
|
||||
new ScriptChunk(ScriptOpCodes.OP_EQUALVERIFY, null, 23),
|
||||
new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, null, 24),
|
||||
new ScriptChunk(ScriptOpCodes.OP_DUP, null),
|
||||
new ScriptChunk(ScriptOpCodes.OP_HASH160, null),
|
||||
new ScriptChunk(ScriptOpCodes.OP_EQUALVERIFY, null),
|
||||
new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, null),
|
||||
};
|
||||
|
||||
void parse() {
|
||||
|
@ -52,7 +52,6 @@ public class Script {
|
|||
ByteArrayInputStream bis = new ByteArrayInputStream(program);
|
||||
int initialSize = bis.available();
|
||||
while (bis.available() > 0) {
|
||||
int startLocationInProgram = initialSize - bis.available();
|
||||
int opcode = bis.read();
|
||||
|
||||
long dataToRead = -1;
|
||||
|
@ -75,7 +74,7 @@ public class Script {
|
|||
|
||||
ScriptChunk chunk;
|
||||
if (dataToRead == -1) {
|
||||
chunk = new ScriptChunk(opcode, null, startLocationInProgram);
|
||||
chunk = new ScriptChunk(opcode, null);
|
||||
} else {
|
||||
if (dataToRead > bis.available())
|
||||
throw new ProtocolException("Push of data element that is larger than remaining data");
|
||||
|
@ -84,7 +83,7 @@ public class Script {
|
|||
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.
|
||||
for (ScriptChunk c : STANDARD_TRANSACTION_SCRIPT_CHUNKS) {
|
||||
|
|
|
@ -22,16 +22,38 @@ public class ScriptChunk {
|
|||
*/
|
||||
public final byte[] data;
|
||||
|
||||
private int startLocationInProgram;
|
||||
|
||||
public ScriptChunk(int opcode, byte[] data) {
|
||||
this(opcode, data, -1);
|
||||
}
|
||||
|
||||
public ScriptChunk(int opcode, byte[] data, int startLocationInProgram) {
|
||||
this.opcode = opcode;
|
||||
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) {
|
||||
|
|
|
@ -92,6 +92,33 @@ public enum ScriptType {
|
|||
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
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(SINGLE);
|
||||
|
@ -171,6 +198,35 @@ public enum ScriptType {
|
|||
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
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(SINGLE);
|
||||
|
@ -218,6 +274,10 @@ public enum ScriptType {
|
|||
|
||||
@Override
|
||||
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<>();
|
||||
for(ECKey key : pubKeys) {
|
||||
pubKeyBytes.add(key.getPubKey());
|
||||
|
@ -310,6 +370,42 @@ public enum ScriptType {
|
|||
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
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(MULTI);
|
||||
|
@ -395,6 +491,41 @@ public enum ScriptType {
|
|||
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
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(MULTI);
|
||||
|
@ -461,6 +592,38 @@ public enum ScriptType {
|
|||
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
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(SINGLE);
|
||||
|
@ -523,6 +686,40 @@ public enum ScriptType {
|
|||
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
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(MULTI, CUSTOM);
|
||||
|
@ -593,6 +790,36 @@ public enum ScriptType {
|
|||
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
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(SINGLE);
|
||||
|
@ -667,6 +894,38 @@ public enum ScriptType {
|
|||
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
|
||||
public List<PolicyType> getAllowedPolicyTypes() {
|
||||
return List.of(MULTI, CUSTOM);
|
||||
|
@ -761,12 +1020,30 @@ public enum ScriptType {
|
|||
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 List<ScriptType> getScriptTypesForPolicyType(PolicyType policyType) {
|
||||
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
|
||||
public String toString() {
|
||||
return name;
|
||||
|
|
|
@ -33,6 +33,13 @@ public class Transaction extends ChildMessage {
|
|||
private ArrayList<TransactionInput> inputs;
|
||||
private ArrayList<TransactionOutput> outputs;
|
||||
|
||||
public Transaction() {
|
||||
version = 1;
|
||||
inputs = new ArrayList<>();
|
||||
outputs = new ArrayList<>();
|
||||
length = 8;
|
||||
}
|
||||
|
||||
public Transaction(byte[] rawtx) {
|
||||
super(rawtx, 0);
|
||||
}
|
||||
|
@ -125,6 +132,11 @@ public class Transaction extends ChildMessage {
|
|||
}
|
||||
|
||||
public void setSegwitVersion(int segwitVersion) {
|
||||
if(!segwit) {
|
||||
adjustLength(2);
|
||||
this.segwit = true;
|
||||
}
|
||||
|
||||
this.segwitVersion = segwitVersion;
|
||||
}
|
||||
|
||||
|
@ -272,10 +284,44 @@ public class Transaction extends ChildMessage {
|
|||
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() {
|
||||
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 {
|
||||
if (inputs.size() == 0 || outputs.size() == 0)
|
||||
throw new VerificationException.EmptyInputsOrOutputs();
|
||||
|
|
|
@ -25,6 +25,23 @@ public class TransactionInput extends ChildMessage {
|
|||
|
||||
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) {
|
||||
super(rawtx, offset);
|
||||
setParent(transaction);
|
||||
|
|
|
@ -18,6 +18,12 @@ public class TransactionOutPoint extends ChildMessage {
|
|||
|
||||
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) {
|
||||
super(rawtx, offset);
|
||||
setParent(parent);
|
||||
|
|
|
@ -3,7 +3,6 @@ package com.sparrowwallet.drongo.protocol;
|
|||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.address.Address;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
@ -19,27 +18,22 @@ public class TransactionOutput extends ChildMessage {
|
|||
|
||||
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) {
|
||||
super(rawtx, offset);
|
||||
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 {
|
||||
value = readInt64();
|
||||
int scriptLen = (int) readVarInt();
|
||||
|
@ -78,6 +72,11 @@ public class TransactionOutput extends ChildMessage {
|
|||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
public Sha256Hash getHash() {
|
||||
Transaction transaction = (Transaction)parent;
|
||||
return transaction.getTxId();
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
Transaction transaction = (Transaction)parent;
|
||||
return transaction.getOutputs().indexOf(this);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.sparrowwallet.drongo.protocol;
|
||||
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
@ -13,6 +14,30 @@ import java.util.List;
|
|||
public class TransactionWitness extends ChildMessage {
|
||||
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) {
|
||||
super(rawtx, offset);
|
||||
setParent(parent);
|
||||
|
@ -53,9 +78,13 @@ public class TransactionWitness extends ChildMessage {
|
|||
int length = new VarInt(pushes.size()).getSizeInBytes();
|
||||
for (int i = 0; i < pushes.size(); i++) {
|
||||
byte[] push = pushes.get(i);
|
||||
if(push.length == 1 && push[0] == 0) {
|
||||
length++;
|
||||
} else {
|
||||
length += new VarInt(push.length).getSizeInBytes();
|
||||
length += push.length;
|
||||
}
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
@ -64,10 +93,14 @@ public class TransactionWitness extends ChildMessage {
|
|||
stream.write(new VarInt(pushes.size()).encode());
|
||||
for (int i = 0; i < pushes.size(); 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(push);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] toByteArray() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
purposeNodes.clear();
|
||||
transactions.clear();
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue