mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 01:56:44 +00:00
wallet implementation first commit
This commit is contained in:
parent
0cebee3d22
commit
08e8df0807
12 changed files with 592 additions and 49 deletions
|
@ -6,6 +6,7 @@ import com.sparrowwallet.drongo.crypto.DeterministicKey;
|
|||
import com.sparrowwallet.drongo.protocol.Script;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptChunk;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptOpCodes;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -117,8 +118,7 @@ public class OutputDescriptor {
|
|||
if(script.equals("pkh")) {
|
||||
address = new P2PKHAddress(childKey.getPubKeyHash());
|
||||
} else if(script.equals("sh(wpkh")) {
|
||||
Address p2wpkhAddress = new P2WPKHAddress(childKey.getPubKeyHash());
|
||||
Script receivingP2wpkhScript = p2wpkhAddress.getOutputScript();
|
||||
Script receivingP2wpkhScript = ScriptType.P2WPKH.getOutputScript(childKey.getPubKeyHash());
|
||||
address = P2SHAddress.fromProgram(receivingP2wpkhScript.getProgram());
|
||||
} else if(script.equals("wpkh")) {
|
||||
address = new P2WPKHAddress(childKey.getPubKeyHash());
|
||||
|
|
37
src/main/java/com/sparrowwallet/drongo/policy/Policy.java
Normal file
37
src/main/java/com/sparrowwallet/drongo/policy/Policy.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package com.sparrowwallet.drongo.protocol;
|
|||
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.address.*;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
@ -12,6 +13,8 @@ import java.util.Arrays;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.sparrowwallet.drongo.protocol.ScriptType.*;
|
||||
|
||||
import static com.sparrowwallet.drongo.protocol.ScriptOpCodes.*;
|
||||
|
||||
public class Script {
|
||||
|
@ -124,7 +127,13 @@ public class Script {
|
|||
* Returns true if this script has the required form to contain a destination address
|
||||
*/
|
||||
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>
|
||||
*/
|
||||
public byte[] getPubKeyHash() throws ProtocolException {
|
||||
if (ScriptPattern.isP2PKH(this))
|
||||
return ScriptPattern.extractHashFromP2PKH(this);
|
||||
else if (ScriptPattern.isP2SH(this))
|
||||
return ScriptPattern.extractHashFromP2SH(this);
|
||||
else if (ScriptPattern.isP2WPKH(this) || ScriptPattern.isP2WSH(this))
|
||||
return ScriptPattern.extractHashFromP2WH(this);
|
||||
else
|
||||
throw new ProtocolException("Script not in the standard scriptPubKey form");
|
||||
for(ScriptType scriptType : SINGLE_HASH_TYPES) {
|
||||
if(scriptType.isScriptType(this)) {
|
||||
return scriptType.getHashFromScript(this);
|
||||
}
|
||||
}
|
||||
|
||||
throw new ProtocolException("Script not a standard form that contains a single hash");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the destination address from this script, if it's in the required form.
|
||||
*/
|
||||
public Address[] getToAddresses() throws NonStandardScriptException {
|
||||
if (ScriptPattern.isP2PK(this))
|
||||
return new Address[] { new P2PKAddress( ScriptPattern.extractPKFromP2PK(this).getPubKey()) };
|
||||
else if (ScriptPattern.isP2PKH(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))
|
||||
return new Address[] { new P2WPKHAddress(ScriptPattern.extractHashFromP2WH(this)) };
|
||||
else if (ScriptPattern.isP2WSH(this))
|
||||
return new Address[] { new P2WSHAddress(ScriptPattern.extractHashFromP2WH(this)) };
|
||||
else if (ScriptPattern.isMultisig(this))
|
||||
return ScriptPattern.extractMultisigAddresses(this);
|
||||
else
|
||||
throw new NonStandardScriptException("Cannot find addresses in non standard script: " + toString());
|
||||
for(ScriptType scriptType : SINGLE_HASH_TYPES) {
|
||||
if(scriptType.isScriptType(this)) {
|
||||
return new Address[] { scriptType.getAddress(scriptType.getHashFromScript(this)) };
|
||||
}
|
||||
}
|
||||
|
||||
if(P2PK.isScriptType(this)) {
|
||||
return new Address[] { P2PK.getAddress(P2PK.getPublicKeyFromScript(this).getPubKey()) };
|
||||
}
|
||||
|
||||
if(MULTISIG.isScriptType(this)) {
|
||||
List<Address> addresses = new ArrayList<>();
|
||||
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());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if(ScriptPattern.isMultisig(this)) {
|
||||
return ScriptPattern.extractMultisigThreshold(this);
|
||||
if(MULTISIG.isScriptType(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() {
|
||||
|
@ -289,9 +304,9 @@ public class Script {
|
|||
builder.append("<signature").append(signatureCount++).append(">");
|
||||
} else if(chunk.isScript()) {
|
||||
Script nestedScript = chunk.getScript();
|
||||
if(ScriptPattern.isP2WPKH(nestedScript)) {
|
||||
if(P2WPKH.isScriptType(nestedScript)) {
|
||||
builder.append("(OP_0 <wpkh>)");
|
||||
} else if(ScriptPattern.isP2WSH(nestedScript)) {
|
||||
} else if(P2WSH.isScriptType(nestedScript)) {
|
||||
builder.append("(OP_0 <wsh>)");
|
||||
} else {
|
||||
builder.append("(").append(nestedScript.toDisplayString()).append(")");
|
||||
|
|
387
src/main/java/com/sparrowwallet/drongo/protocol/ScriptType.java
Normal file
387
src/main/java/com/sparrowwallet/drongo/protocol/ScriptType.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -544,8 +544,7 @@ public class Transaction extends TransactionPart {
|
|||
Transaction transaction = new Transaction(transactionBytes);
|
||||
|
||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880ae"));
|
||||
P2PKHAddress address = new P2PKHAddress(pubKey.getPubKeyHash());
|
||||
System.out.println(address.getOutputScript().getProgram().length);
|
||||
System.out.println(ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()).getProgram().length);
|
||||
Script script = new Script(Utils.hexToBytes("21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac"));
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(1, script,4900000000L, SigHash.SINGLE, false);
|
||||
System.out.println("Sighash: " + hash.toString());
|
||||
|
|
|
@ -2,8 +2,6 @@ package com.sparrowwallet.drongo.psbt;
|
|||
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
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.protocol.*;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
@ -13,6 +11,7 @@ import org.slf4j.LoggerFactory;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
import static com.sparrowwallet.drongo.protocol.ScriptType.*;
|
||||
import static com.sparrowwallet.drongo.psbt.PSBTEntry.parseKeyDerivation;
|
||||
|
||||
public class PSBTInput {
|
||||
|
@ -80,7 +79,7 @@ public class PSBTInput {
|
|||
throw new PSBTParseException("Cannot have both witness and non-witness utxos in PSBT input");
|
||||
}
|
||||
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");
|
||||
}
|
||||
this.witnessUtxo = witnessTxOutput;
|
||||
|
@ -113,11 +112,11 @@ public class PSBTInput {
|
|||
scriptPubKey = this.nonWitnessUtxo.getOutputs().get((int)transaction.getInputs().get(index).getOutpoint().getIndex()).getScript();
|
||||
} else if(this.witnessUtxo != null) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
if(!Arrays.equals(Utils.sha256hash160(redeemScript.getProgram()), scriptPubKey.getPubKeyHash())) {
|
||||
|
@ -131,9 +130,9 @@ public class PSBTInput {
|
|||
entry.checkOneByteKey();
|
||||
Script witnessScript = new Script(entry.getData());
|
||||
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();
|
||||
} 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();
|
||||
}
|
||||
if(pubKeyHash == null) {
|
||||
|
@ -285,7 +284,7 @@ public class PSBTInput {
|
|||
int vout = (int)transaction.getInputs().get(index).getOutpoint().getIndex();
|
||||
Script signingScript = getNonWitnessUtxo() != null ? getNonWitnessUtxo().getOutputs().get(vout).getScript() : getWitnessUtxo().getScript();
|
||||
|
||||
if(ScriptPattern.isP2SH(signingScript)) {
|
||||
if(P2SH.isScriptType(signingScript)) {
|
||||
if(getRedeemScript() != null) {
|
||||
signingScript = getRedeemScript();
|
||||
} else if(getFinalScriptSig() != null) {
|
||||
|
@ -295,10 +294,9 @@ public class PSBTInput {
|
|||
}
|
||||
}
|
||||
|
||||
if(ScriptPattern.isP2WPKH(signingScript)) {
|
||||
Address address = new P2PKHAddress(signingScript.getPubKeyHash());
|
||||
signingScript = address.getOutputScript();
|
||||
} else if(ScriptPattern.isP2WSH(signingScript)) {
|
||||
if(P2WPKH.isScriptType(signingScript)) {
|
||||
signingScript = ScriptType.P2PKH.getOutputScript(signingScript.getPubKeyHash());
|
||||
} else if(P2WSH.isScriptType(signingScript)) {
|
||||
if(getWitnessScript() != null) {
|
||||
signingScript = getWitnessScript();
|
||||
} else if(getFinalScriptWitness() != null && getFinalScriptWitness().getWitnessScript() != null) {
|
||||
|
|
|
@ -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;
|
||||
}
|
46
src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java
Normal file
46
src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -6,4 +6,6 @@ module com.sparrowwallet.drongo {
|
|||
exports com.sparrowwallet.drongo.protocol;
|
||||
exports com.sparrowwallet.drongo.address;
|
||||
exports com.sparrowwallet.drongo.crypto;
|
||||
exports com.sparrowwallet.drongo.wallet;
|
||||
exports com.sparrowwallet.drongo.policy;
|
||||
}
|
|
@ -14,8 +14,7 @@ public class TransactionTest {
|
|||
Transaction transaction = new Transaction(Utils.hexToBytes(hex));
|
||||
|
||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"));
|
||||
P2PKHAddress address = new P2PKHAddress(pubKey.getPubKeyHash());
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(1, address.getOutputScript(),600000000L, Transaction.SigHash.ALL, false);
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(1, ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()),600000000L, Transaction.SigHash.ALL, false);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee"), false, true);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
@ -26,8 +25,7 @@ public class TransactionTest {
|
|||
Transaction transaction = new Transaction(Utils.hexToBytes(hex));
|
||||
|
||||
ECKey pubKey = ECKey.fromPublicOnly(Utils.hexToBytes("03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873"));
|
||||
Address address = new P2PKHAddress(pubKey.getPubKeyHash());
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, address.getOutputScript(),1000000000L, Transaction.SigHash.ALL, false);
|
||||
Sha256Hash hash = transaction.hashForWitnessSignature(0, ScriptType.P2PKH.getOutputScript(pubKey.getPubKeyHash()),1000000000L, Transaction.SigHash.ALL, false);
|
||||
TransactionSignature signature = TransactionSignature.decodeFromBitcoin(Utils.hexToBytes("3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb01"), true, true);
|
||||
Assert.assertTrue(pubKey.verify(hash, signature));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue