wallet implementation first commit

This commit is contained in:
Craig Raw 2020-04-17 10:02:30 +02:00
parent 0cebee3d22
commit 08e8df0807
12 changed files with 592 additions and 49 deletions

View file

@ -6,6 +6,7 @@ import com.sparrowwallet.drongo.crypto.DeterministicKey;
import com.sparrowwallet.drongo.protocol.Script; import com.sparrowwallet.drongo.protocol.Script;
import com.sparrowwallet.drongo.protocol.ScriptChunk; import com.sparrowwallet.drongo.protocol.ScriptChunk;
import com.sparrowwallet.drongo.protocol.ScriptOpCodes; import com.sparrowwallet.drongo.protocol.ScriptOpCodes;
import com.sparrowwallet.drongo.protocol.ScriptType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -117,8 +118,7 @@ public class OutputDescriptor {
if(script.equals("pkh")) { if(script.equals("pkh")) {
address = new P2PKHAddress(childKey.getPubKeyHash()); address = new P2PKHAddress(childKey.getPubKeyHash());
} else if(script.equals("sh(wpkh")) { } else if(script.equals("sh(wpkh")) {
Address p2wpkhAddress = new P2WPKHAddress(childKey.getPubKeyHash()); Script receivingP2wpkhScript = ScriptType.P2WPKH.getOutputScript(childKey.getPubKeyHash());
Script receivingP2wpkhScript = p2wpkhAddress.getOutputScript();
address = P2SHAddress.fromProgram(receivingP2wpkhScript.getProgram()); address = P2SHAddress.fromProgram(receivingP2wpkhScript.getProgram());
} else if(script.equals("wpkh")) { } else if(script.equals("wpkh")) {
address = new P2WPKHAddress(childKey.getPubKeyHash()); address = new P2WPKHAddress(childKey.getPubKeyHash());

View file

@ -0,0 +1,37 @@
package com.sparrowwallet.drongo.policy;
import com.sparrowwallet.drongo.protocol.ScriptType;
import static com.sparrowwallet.drongo.protocol.ScriptType.*;
import static com.sparrowwallet.drongo.policy.PolicyType.*;
public class Policy {
private String policy;
public Policy(String policy) {
this.policy = policy;
}
public String getPolicy() {
return policy;
}
public void setPolicy(String policy) {
this.policy = policy;
}
public static Policy getPolicy(PolicyType policyType, ScriptType scriptType, Integer threshold, Integer numCosigners) {
if(SINGLE.equals(policyType)) {
if(P2PK.equals(scriptType)) {
return new Policy("pk(<key1>)");
}
return new Policy("pkh(<key1>)");
}
if(MULTI.equals(policyType)) {
return new Policy("multi(<threshold>,<key1>,<key2>)");
}
throw new PolicyException("No standard policy for " + policyType + " policy with script type " + scriptType);
}
}

View file

@ -0,0 +1,22 @@
package com.sparrowwallet.drongo.policy;
public class PolicyException extends RuntimeException {
public PolicyException() {
}
public PolicyException(String message) {
super(message);
}
public PolicyException(String message, Throwable cause) {
super(message, cause);
}
public PolicyException(Throwable cause) {
super(cause);
}
public PolicyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -0,0 +1,30 @@
package com.sparrowwallet.drongo.policy;
import com.sparrowwallet.drongo.protocol.ScriptType;
import static com.sparrowwallet.drongo.protocol.ScriptType.*;
public enum PolicyType {
SINGLE("Single Signature", P2WPKH), MULTI("Multi Signature", P2WSH), CUSTOM("Custom", P2WSH);
private String name;
private ScriptType defaultScriptType;
PolicyType(String name, ScriptType defaultScriptType) {
this.name = name;
this.defaultScriptType = defaultScriptType;
}
public String getName() {
return name;
}
public ScriptType getDefaultScriptType() {
return defaultScriptType;
}
@Override
public String toString() {
return name;
}
}

View file

@ -2,6 +2,7 @@ package com.sparrowwallet.drongo.protocol;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.address.*; import com.sparrowwallet.drongo.address.*;
import com.sparrowwallet.drongo.crypto.ECKey;
import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.encoders.Hex;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -12,6 +13,8 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static com.sparrowwallet.drongo.protocol.ScriptType.*;
import static com.sparrowwallet.drongo.protocol.ScriptOpCodes.*; import static com.sparrowwallet.drongo.protocol.ScriptOpCodes.*;
public class Script { public class Script {
@ -124,7 +127,13 @@ public class Script {
* 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
*/ */
public boolean containsToAddress() { public boolean containsToAddress() {
return ScriptPattern.isP2PK(this) || ScriptPattern.isP2PKH(this) || ScriptPattern.isP2SH(this) || ScriptPattern.isP2WPKH(this) || ScriptPattern.isP2WSH(this) || ScriptPattern.isMultisig(this); for(ScriptType scriptType : ScriptType.values()) {
if(scriptType.isScriptType(this)) {
return true;
}
}
return false;
} }
/** /**
@ -133,46 +142,52 @@ public class Script {
* <p>Otherwise this method throws a ScriptException.</p> * <p>Otherwise this method throws a ScriptException.</p>
*/ */
public byte[] getPubKeyHash() throws ProtocolException { public byte[] getPubKeyHash() throws ProtocolException {
if (ScriptPattern.isP2PKH(this)) for(ScriptType scriptType : SINGLE_HASH_TYPES) {
return ScriptPattern.extractHashFromP2PKH(this); if(scriptType.isScriptType(this)) {
else if (ScriptPattern.isP2SH(this)) return scriptType.getHashFromScript(this);
return ScriptPattern.extractHashFromP2SH(this); }
else if (ScriptPattern.isP2WPKH(this) || ScriptPattern.isP2WSH(this)) }
return ScriptPattern.extractHashFromP2WH(this);
else throw new ProtocolException("Script not a standard form that contains a single hash");
throw new ProtocolException("Script not in the standard scriptPubKey form");
} }
/** /**
* Gets the destination address from this script, if it's in the required form. * Gets the destination address from this script, if it's in the required form.
*/ */
public Address[] getToAddresses() throws NonStandardScriptException { public Address[] getToAddresses() throws NonStandardScriptException {
if (ScriptPattern.isP2PK(this)) for(ScriptType scriptType : SINGLE_HASH_TYPES) {
return new Address[] { new P2PKAddress( ScriptPattern.extractPKFromP2PK(this).getPubKey()) }; if(scriptType.isScriptType(this)) {
else if (ScriptPattern.isP2PKH(this)) return new Address[] { scriptType.getAddress(scriptType.getHashFromScript(this)) };
return new Address[] { new P2PKHAddress( ScriptPattern.extractHashFromP2PKH(this)) }; }
else if (ScriptPattern.isP2SH(this)) }
return new Address[] { new P2SHAddress(ScriptPattern.extractHashFromP2SH(this)) };
else if (ScriptPattern.isP2WPKH(this)) if(P2PK.isScriptType(this)) {
return new Address[] { new P2WPKHAddress(ScriptPattern.extractHashFromP2WH(this)) }; return new Address[] { P2PK.getAddress(P2PK.getPublicKeyFromScript(this).getPubKey()) };
else if (ScriptPattern.isP2WSH(this)) }
return new Address[] { new P2WSHAddress(ScriptPattern.extractHashFromP2WH(this)) };
else if (ScriptPattern.isMultisig(this)) if(MULTISIG.isScriptType(this)) {
return ScriptPattern.extractMultisigAddresses(this); List<Address> addresses = new ArrayList<>();
else ECKey[] pubKeys = MULTISIG.getPublicKeysFromScript(this);
for(ECKey pubKey : pubKeys) {
addresses.add(new P2PKAddress(pubKey.getPubKey()));
}
return addresses.toArray(new Address[addresses.size()]);
}
throw new NonStandardScriptException("Cannot find addresses in non standard script: " + toString()); throw new NonStandardScriptException("Cannot find addresses in non standard script: " + toString());
} }
public int getNumRequiredSignatures() throws NonStandardScriptException { public int getNumRequiredSignatures() throws NonStandardScriptException {
if(ScriptPattern.isP2PK(this) || ScriptPattern.isP2PKH(this) || ScriptPattern.isP2WPKH(this)) { if(P2PK.isScriptType(this) || P2PKH.isScriptType(this) || P2WPKH.isScriptType(this)) {
return 1; return 1;
} }
if(ScriptPattern.isMultisig(this)) { if(MULTISIG.isScriptType(this)) {
return ScriptPattern.extractMultisigThreshold(this); return MULTISIG.getThreshold(this);
} }
throw new NonStandardScriptException("Cannot find number of required signatures for non standard script: " + toString()); throw new NonStandardScriptException("Cannot find number of required signatures for script: " + toString());
} }
public Script getFirstNestedScript() { public Script getFirstNestedScript() {
@ -289,9 +304,9 @@ public class Script {
builder.append("<signature").append(signatureCount++).append(">"); builder.append("<signature").append(signatureCount++).append(">");
} else if(chunk.isScript()) { } else if(chunk.isScript()) {
Script nestedScript = chunk.getScript(); Script nestedScript = chunk.getScript();
if(ScriptPattern.isP2WPKH(nestedScript)) { if(P2WPKH.isScriptType(nestedScript)) {
builder.append("(OP_0 <wpkh>)"); builder.append("(OP_0 <wpkh>)");
} else if(ScriptPattern.isP2WSH(nestedScript)) { } else if(P2WSH.isScriptType(nestedScript)) {
builder.append("(OP_0 <wsh>)"); builder.append("(OP_0 <wsh>)");
} else { } else {
builder.append("(").append(nestedScript.toDisplayString()).append(")"); builder.append("(").append(nestedScript.toDisplayString()).append(")");

View file

@ -0,0 +1,387 @@
package com.sparrowwallet.drongo.protocol;
import com.sparrowwallet.drongo.address.*;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.policy.PolicyType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.sparrowwallet.drongo.policy.PolicyType.*;
import static com.sparrowwallet.drongo.protocol.Script.decodeFromOpN;
import static com.sparrowwallet.drongo.protocol.ScriptOpCodes.*;
public enum ScriptType {
P2PK("P2PK", new PolicyType[]{SINGLE}) {
@Override
public Address getAddress(byte[] pubKey) {
return new P2PKAddress(pubKey);
}
@Override
public Address[] getAddresses(Script script) {
return new Address[] { getAddress(getPublicKeyFromScript(script).getPubKey()) };
}
@Override
public Script getOutputScript(byte[] pubKey) {
List<ScriptChunk> chunks = new ArrayList<>();
chunks.add(new ScriptChunk(pubKey.length, pubKey));
chunks.add(new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, null));
return new Script(chunks);
}
@Override
public boolean isScriptType(Script script) {
List<ScriptChunk> chunks = script.chunks;
if (chunks.size() != 2)
return false;
if (!chunks.get(0).equalsOpCode(0x21) && !chunks.get(0).equalsOpCode(0x41))
return false;
byte[] chunk2data = chunks.get(0).data;
if (chunk2data == null)
return false;
if (chunk2data.length != 33 && chunk2data.length != 65)
return false;
if (!chunks.get(1).equalsOpCode(OP_CHECKSIG))
return false;
return true;
}
@Override
public byte[] getHashFromScript(Script script) {
throw new ProtocolException("P2PK script does contain hash, use getPublicKeyFromScript(script) to retreive public key");
}
@Override
public ECKey getPublicKeyFromScript(Script script) {
return ECKey.fromPublicOnly(script.chunks.get(0).data);
}
},
P2PKH("P2PKH", new PolicyType[]{SINGLE}) {
@Override
public Address getAddress(byte[] pubKeyHash) {
return new P2PKHAddress(pubKeyHash);
}
@Override
public Script getOutputScript(byte[] pubKeyHash) {
List<ScriptChunk> chunks = new ArrayList<>();
chunks.add(new ScriptChunk(ScriptOpCodes.OP_DUP, null));
chunks.add(new ScriptChunk(ScriptOpCodes.OP_HASH160, null));
chunks.add(new ScriptChunk(pubKeyHash.length, pubKeyHash));
chunks.add(new ScriptChunk(ScriptOpCodes.OP_EQUALVERIFY, null));
chunks.add(new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, null));
return new Script(chunks);
}
@Override
public boolean isScriptType(Script script) {
List<ScriptChunk> chunks = script.chunks;
if (chunks.size() != 5)
return false;
if (!chunks.get(0).equalsOpCode(OP_DUP))
return false;
if (!chunks.get(1).equalsOpCode(OP_HASH160))
return false;
byte[] chunk2data = chunks.get(2).data;
if (chunk2data == null)
return false;
if (chunk2data.length != 20)
return false;
if (!chunks.get(3).equalsOpCode(OP_EQUALVERIFY))
return false;
if (!chunks.get(4).equalsOpCode(OP_CHECKSIG))
return false;
return true;
}
@Override
public byte[] getHashFromScript(Script script) {
return script.chunks.get(2).data;
}
},
MULTISIG("Bare Multisig", new PolicyType[]{MULTI}) {
@Override
public Address getAddress(byte[] bytes) {
throw new ProtocolException("No single address for multisig script type");
}
@Override
public Address[] getAddresses(Script script) {
return Arrays.stream(getPublicKeysFromScript(script)).map(pubKey -> new P2PKAddress(pubKey.getPubKey())).toArray(Address[]::new);
}
@Override
public Script getOutputScript(byte[] bytes) {
throw new ProtocolException("Output script for multisig script type must be constructed with method getOutputScript(int threshold, byte[] pubKey1, byte[] pubKey2, ...)");
}
public Script getOutputScript(int threshold, byte[] ...pubKeys) {
List<ScriptChunk> chunks = new ArrayList<>();
chunks.add(new ScriptChunk(Script.encodeToOpN(threshold), null));
for(byte[] pubKey : pubKeys) {
chunks.add(new ScriptChunk(pubKey.length, pubKey));
}
chunks.add(new ScriptChunk(Script.encodeToOpN(pubKeys.length), null));
chunks.add(new ScriptChunk(ScriptOpCodes.OP_CHECKMULTISIG, null));
return new Script(chunks);
}
@Override
public boolean isScriptType(Script script) {
List<ScriptChunk> chunks = script.chunks;
if (chunks.size() < 4) return false;
ScriptChunk chunk = chunks.get(chunks.size() - 1);
// Must end in OP_CHECKMULTISIG[VERIFY].
if (!chunk.isOpCode()) return false;
if (!(chunk.equalsOpCode(OP_CHECKMULTISIG) || chunk.equalsOpCode(OP_CHECKMULTISIGVERIFY))) return false;
try {
// Second to last chunk must be an OP_N opcode and there should be that many data chunks (keys).
ScriptChunk m = chunks.get(chunks.size() - 2);
if (!m.isOpCode()) return false;
int numKeys = Script.decodeFromOpN(m.opcode);
if (numKeys < 1 || chunks.size() != 3 + numKeys) return false;
for (int i = 1; i < chunks.size() - 2; i++) {
if (chunks.get(i).isOpCode()) return false;
}
// First chunk must be an OP_N opcode too.
if (Script.decodeFromOpN(chunks.get(0).opcode) < 1) return false;
} catch (IllegalStateException e) {
return false; // Not an OP_N opcode.
}
return true;
}
@Override
public byte[] getHashFromScript(Script script) {
throw new ProtocolException("Public keys for bare multisig script type must be retrieved with method getPublicKeysFromScript(Script script)");
}
@Override
public ECKey[] getPublicKeysFromScript(Script script) {
List<ECKey> pubKeys = new ArrayList<>();
List<ScriptChunk> chunks = script.chunks;
for (int i = 1; i < chunks.size() - 2; i++) {
byte[] pubKey = chunks.get(i).data;
pubKeys.add(ECKey.fromPublicOnly(pubKey));
}
return pubKeys.toArray(new ECKey[pubKeys.size()]);
}
@Override
public int getThreshold(Script script) {
return decodeFromOpN(script.chunks.get(0).opcode);
}
},
P2SH("P2SH", new PolicyType[]{MULTI}) {
@Override
public Address getAddress(byte[] bytes) {
return new P2SHAddress(bytes);
}
@Override
public Script getOutputScript(byte[] bytes) {
List<ScriptChunk> chunks = new ArrayList<>();
chunks.add(new ScriptChunk(ScriptOpCodes.OP_HASH160, null));
chunks.add(new ScriptChunk(bytes.length, bytes));
chunks.add(new ScriptChunk(ScriptOpCodes.OP_EQUAL, null));
return new Script(chunks);
}
@Override
public boolean isScriptType(Script script) {
List<ScriptChunk> chunks = script.chunks;
// We check for the effective serialized form because BIP16 defines a P2SH output using an exact byte
// template, not the logical program structure. Thus you can have two programs that look identical when
// printed out but one is a P2SH script and the other isn't! :(
// We explicitly test that the op code used to load the 20 bytes is 0x14 and not something logically
// equivalent like {@code OP_HASH160 OP_PUSHDATA1 0x14 <20 bytes of script hash> OP_EQUAL}
if (chunks.size() != 3)
return false;
if (!chunks.get(0).equalsOpCode(OP_HASH160))
return false;
ScriptChunk chunk1 = chunks.get(1);
if (chunk1.opcode != 0x14)
return false;
byte[] chunk1data = chunk1.data;
if (chunk1data == null)
return false;
if (chunk1data.length != 20)
return false;
if (!chunks.get(2).equalsOpCode(OP_EQUAL))
return false;
return true;
}
@Override
public byte[] getHashFromScript(Script script) {
return script.chunks.get(1).data;
}
},
P2SH_P2WPKH("P2SH-P2WPKH", new PolicyType[]{SINGLE}) {
@Override
public Address getAddress(byte[] bytes) {
return P2SH.getAddress(bytes);
}
@Override
public Script getOutputScript(byte[] bytes) {
return P2SH.getOutputScript(bytes);
}
@Override
public boolean isScriptType(Script script) {
return P2SH.isScriptType(script);
}
@Override
public byte[] getHashFromScript(Script script) {
return P2SH.getHashFromScript(script);
}
},
P2SH_P2WSH("P2SH-P2WSH", new PolicyType[]{MULTI, CUSTOM}) {
@Override
public Address getAddress(byte[] bytes) {
return P2SH.getAddress(bytes);
}
@Override
public Script getOutputScript(byte[] bytes) {
return P2SH.getOutputScript(bytes);
}
@Override
public boolean isScriptType(Script script) {
return P2SH.isScriptType(script);
}
@Override
public byte[] getHashFromScript(Script script) {
return P2SH.getHashFromScript(script);
}
},
P2WPKH("P2WPKH", new PolicyType[]{SINGLE}) {
@Override
public Address getAddress(byte[] bytes) {
return new P2WPKHAddress(bytes);
}
@Override
public Script getOutputScript(byte[] bytes) {
List<ScriptChunk> chunks = new ArrayList<>();
chunks.add(new ScriptChunk(OP_0, null));
chunks.add(new ScriptChunk(bytes.length, bytes));
return new Script(chunks);
}
@Override
public boolean isScriptType(Script script) {
List<ScriptChunk> chunks = script.chunks;
if (chunks.size() != 2)
return false;
if (!chunks.get(0).equalsOpCode(OP_0))
return false;
byte[] chunk1data = chunks.get(1).data;
if (chunk1data == null)
return false;
if (chunk1data.length != 20)
return false;
return true;
}
@Override
public byte[] getHashFromScript(Script script) {
return script.chunks.get(1).data;
}
},
P2WSH("P2WSH", new PolicyType[]{MULTI, CUSTOM}) {
@Override
public Address getAddress(byte[] bytes) {
return new P2WSHAddress(bytes);
}
@Override
public Script getOutputScript(byte[] bytes) {
List<ScriptChunk> chunks = new ArrayList<>();
chunks.add(new ScriptChunk(OP_0, null));
chunks.add(new ScriptChunk(bytes.length, bytes));
return new Script(chunks);
}
@Override
public boolean isScriptType(Script script) {
List<ScriptChunk> chunks = script.chunks;
if (chunks.size() != 2)
return false;
if (!chunks.get(0).equalsOpCode(OP_0))
return false;
byte[] chunk1data = chunks.get(1).data;
if (chunk1data == null)
return false;
if (chunk1data.length != 32)
return false;
return true;
}
@Override
public byte[] getHashFromScript(Script script) {
return script.chunks.get(1).data;
}
};
private final String name;
private final PolicyType[] allowedPolicyTypes;
ScriptType(String name, PolicyType[] allowedPolicyTypes) {
this.name = name;
this.allowedPolicyTypes = allowedPolicyTypes;
}
public String getName() {
return name;
}
public PolicyType[] getAllowedPolicyTypes() {
return allowedPolicyTypes;
}
public abstract Address getAddress(byte[] bytes);
public abstract Script getOutputScript(byte[] bytes);
public abstract boolean isScriptType(Script script);
public abstract byte[] getHashFromScript(Script script);
public Address[] getAddresses(Script script) {
return new Address[] { getAddress(getHashFromScript(script)) };
}
public ECKey getPublicKeyFromScript(Script script) {
throw new ProtocolException("Script type " + this + " does not contain a public key");
}
public ECKey[] getPublicKeysFromScript(Script script) {
throw new ProtocolException("Script type " + this + " does not contain public keys");
}
public int getThreshold(Script script) {
throw new ProtocolException("Script type " + this + " is not a multisig script");
}
public static final ScriptType[] SINGLE_HASH_TYPES = {P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH};
@Override
public String toString() {
return name;
}
}

View file

@ -544,8 +544,7 @@ public class Transaction extends TransactionPart {
Transaction transaction = new Transaction(transactionBytes); Transaction transaction = new Transaction(transactionBytes);
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880ae")); ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880ae"));
P2PKHAddress address = new P2PKHAddress(pubKey.getPubKeyHash()); System.out.println(ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()).getProgram().length);
System.out.println(address.getOutputScript().getProgram().length);
Script script = new Script(Utils.hexToBytes("21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac")); Script script = new Script(Utils.hexToBytes("21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac"));
Sha256Hash hash = transaction.hashForWitnessSignature(1, script,4900000000L, SigHash.SINGLE, false); Sha256Hash hash = transaction.hashForWitnessSignature(1, script,4900000000L, SigHash.SINGLE, false);
System.out.println("Sighash: " + hash.toString()); System.out.println("Sighash: " + hash.toString());

View file

@ -2,8 +2,6 @@ package com.sparrowwallet.drongo.psbt;
import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.address.P2PKHAddress;
import com.sparrowwallet.drongo.crypto.ECKey; import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.protocol.*; import com.sparrowwallet.drongo.protocol.*;
import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.encoders.Hex;
@ -13,6 +11,7 @@ import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import static com.sparrowwallet.drongo.protocol.ScriptType.*;
import static com.sparrowwallet.drongo.psbt.PSBTEntry.parseKeyDerivation; import static com.sparrowwallet.drongo.psbt.PSBTEntry.parseKeyDerivation;
public class PSBTInput { public class PSBTInput {
@ -80,7 +79,7 @@ public class PSBTInput {
throw new PSBTParseException("Cannot have both witness and non-witness utxos in PSBT input"); throw new PSBTParseException("Cannot have both witness and non-witness utxos in PSBT input");
} }
TransactionOutput witnessTxOutput = new TransactionOutput(null, entry.getData(), 0); TransactionOutput witnessTxOutput = new TransactionOutput(null, entry.getData(), 0);
if(!ScriptPattern.isP2SH(witnessTxOutput.getScript()) && !ScriptPattern.isP2WPKH(witnessTxOutput.getScript()) && !ScriptPattern.isP2WSH(witnessTxOutput.getScript())) { if(!P2SH.isScriptType(witnessTxOutput.getScript()) && !P2WPKH.isScriptType(witnessTxOutput.getScript()) && !P2WSH.isScriptType(witnessTxOutput.getScript())) {
throw new PSBTParseException("Witness UTXO provided for non-witness or unknown input"); throw new PSBTParseException("Witness UTXO provided for non-witness or unknown input");
} }
this.witnessUtxo = witnessTxOutput; this.witnessUtxo = witnessTxOutput;
@ -113,11 +112,11 @@ public class PSBTInput {
scriptPubKey = this.nonWitnessUtxo.getOutputs().get((int)transaction.getInputs().get(index).getOutpoint().getIndex()).getScript(); scriptPubKey = this.nonWitnessUtxo.getOutputs().get((int)transaction.getInputs().get(index).getOutpoint().getIndex()).getScript();
} else if(this.witnessUtxo != null) { } else if(this.witnessUtxo != null) {
scriptPubKey = this.witnessUtxo.getScript(); scriptPubKey = this.witnessUtxo.getScript();
if(!ScriptPattern.isP2WPKH(redeemScript) && !ScriptPattern.isP2WSH(redeemScript)) { //Witness UTXO should only be provided for P2SH-P2WPKH or P2SH-P2WSH if(!P2WPKH.isScriptType(redeemScript) && !P2WSH.isScriptType(redeemScript)) { //Witness UTXO should only be provided for P2SH-P2WPKH or P2SH-P2WSH
throw new PSBTParseException("Witness UTXO provided but redeem script is not P2WPKH or P2WSH"); throw new PSBTParseException("Witness UTXO provided but redeem script is not P2WPKH or P2WSH");
} }
} }
if(scriptPubKey == null || !ScriptPattern.isP2SH(scriptPubKey)) { if(scriptPubKey == null || !P2SH.isScriptType(scriptPubKey)) {
throw new PSBTParseException("PSBT provided a redeem script for a transaction output that does not need one"); throw new PSBTParseException("PSBT provided a redeem script for a transaction output that does not need one");
} }
if(!Arrays.equals(Utils.sha256hash160(redeemScript.getProgram()), scriptPubKey.getPubKeyHash())) { if(!Arrays.equals(Utils.sha256hash160(redeemScript.getProgram()), scriptPubKey.getPubKeyHash())) {
@ -131,9 +130,9 @@ public class PSBTInput {
entry.checkOneByteKey(); entry.checkOneByteKey();
Script witnessScript = new Script(entry.getData()); Script witnessScript = new Script(entry.getData());
byte[] pubKeyHash = null; byte[] pubKeyHash = null;
if(this.redeemScript != null && ScriptPattern.isP2WSH(this.redeemScript)) { //P2SH-P2WSH if(this.redeemScript != null && P2WSH.isScriptType(this.redeemScript)) { //P2SH-P2WSH
pubKeyHash = this.redeemScript.getPubKeyHash(); pubKeyHash = this.redeemScript.getPubKeyHash();
} else if(this.witnessUtxo != null && ScriptPattern.isP2WSH(this.witnessUtxo.getScript())) { //P2WSH } else if(this.witnessUtxo != null && P2WSH.isScriptType(this.witnessUtxo.getScript())) { //P2WSH
pubKeyHash = this.witnessUtxo.getScript().getPubKeyHash(); pubKeyHash = this.witnessUtxo.getScript().getPubKeyHash();
} }
if(pubKeyHash == null) { if(pubKeyHash == null) {
@ -285,7 +284,7 @@ public class PSBTInput {
int vout = (int)transaction.getInputs().get(index).getOutpoint().getIndex(); int vout = (int)transaction.getInputs().get(index).getOutpoint().getIndex();
Script signingScript = getNonWitnessUtxo() != null ? getNonWitnessUtxo().getOutputs().get(vout).getScript() : getWitnessUtxo().getScript(); Script signingScript = getNonWitnessUtxo() != null ? getNonWitnessUtxo().getOutputs().get(vout).getScript() : getWitnessUtxo().getScript();
if(ScriptPattern.isP2SH(signingScript)) { if(P2SH.isScriptType(signingScript)) {
if(getRedeemScript() != null) { if(getRedeemScript() != null) {
signingScript = getRedeemScript(); signingScript = getRedeemScript();
} else if(getFinalScriptSig() != null) { } else if(getFinalScriptSig() != null) {
@ -295,10 +294,9 @@ public class PSBTInput {
} }
} }
if(ScriptPattern.isP2WPKH(signingScript)) { if(P2WPKH.isScriptType(signingScript)) {
Address address = new P2PKHAddress(signingScript.getPubKeyHash()); signingScript = ScriptType.P2PKH.getOutputScript(signingScript.getPubKeyHash());
signingScript = address.getOutputScript(); } else if(P2WSH.isScriptType(signingScript)) {
} else if(ScriptPattern.isP2WSH(signingScript)) {
if(getWitnessScript() != null) { if(getWitnessScript() != null) {
signingScript = getWitnessScript(); signingScript = getWitnessScript();
} else if(getFinalScriptWitness() != null && getFinalScriptWitness().getWitnessScript() != null) { } else if(getFinalScriptWitness() != null && getFinalScriptWitness().getWitnessScript() != null) {

View file

@ -0,0 +1,9 @@
package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.sparrowwallet.drongo.KeyDerivation;
public class Keystore {
private KeyDerivation keyDerivation;
private ExtendedPublicKey extendedPublicKey;
}

View file

@ -0,0 +1,46 @@
package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import java.util.ArrayList;
import java.util.List;
public class Wallet {
private PolicyType policyType;
private ScriptType scriptType;
private String policy;
private List<Keystore> keystores = new ArrayList<>();
public PolicyType getPolicyType() {
return policyType;
}
public void setPolicyType(PolicyType policyType) {
this.policyType = policyType;
}
public ScriptType getScriptType() {
return scriptType;
}
public void setScriptType(ScriptType scriptType) {
this.scriptType = scriptType;
}
public String getPolicy() {
return policy;
}
public void setPolicy(String policy) {
this.policy = policy;
}
public List<Keystore> getKeystores() {
return keystores;
}
public void setKeystores(List<Keystore> keystores) {
this.keystores = keystores;
}
}

View file

@ -6,4 +6,6 @@ module com.sparrowwallet.drongo {
exports com.sparrowwallet.drongo.protocol; exports com.sparrowwallet.drongo.protocol;
exports com.sparrowwallet.drongo.address; exports com.sparrowwallet.drongo.address;
exports com.sparrowwallet.drongo.crypto; exports com.sparrowwallet.drongo.crypto;
exports com.sparrowwallet.drongo.wallet;
exports com.sparrowwallet.drongo.policy;
} }

View file

@ -14,8 +14,7 @@ public class TransactionTest {
Transaction transaction = new Transaction(Utils.hexToBytes(hex)); Transaction transaction = new Transaction(Utils.hexToBytes(hex));
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357")); ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"));
P2PKHAddress address = new P2PKHAddress(pubKey.getPubKeyHash()); Sha256Hash hash = transaction.hashForWitnessSignature(1, ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()),600000000L, Transaction.SigHash.ALL, false);
Sha256Hash hash = transaction.hashForWitnessSignature(1, address.getOutputScript(),600000000L, Transaction.SigHash.ALL, false);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee"), false, true); TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee"), false, true);
Assert.assertTrue(pubKey.verify(hash, signature)); Assert.assertTrue(pubKey.verify(hash, signature));
} }
@ -26,8 +25,7 @@ public class TransactionTest {
Transaction transaction = new Transaction(Utils.hexToBytes(hex)); Transaction transaction = new Transaction(Utils.hexToBytes(hex));
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873")); ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873"));
Address address = new P2PKHAddress(pubKey.getPubKeyHash()); Sha256Hash hash = transaction.hashForWitnessSignature(0, ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()),1000000000L, Transaction.SigHash.ALL, false);
Sha256Hash hash = transaction.hashForWitnessSignature(0, address.getOutputScript(),1000000000L, Transaction.SigHash.ALL, false);
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb01"), true, true); TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb01"), true, true);
Assert.assertTrue(pubKey.verify(hash, signature)); Assert.assertTrue(pubKey.verify(hash, signature));
} }