cleanup script type and patterns

This commit is contained in:
Craig Raw 2020-04-17 10:12:59 +02:00
parent 08e8df0807
commit 764841635c
7 changed files with 16 additions and 268 deletions

View file

@ -1,7 +1,7 @@
package com.sparrowwallet.drongo.address; package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.protocol.Base58; import com.sparrowwallet.drongo.protocol.Base58;
import com.sparrowwallet.drongo.protocol.Script; import com.sparrowwallet.drongo.protocol.ScriptType;
public abstract class Address { public abstract class Address {
protected final byte[] hash; protected final byte[] hash;
@ -24,7 +24,7 @@ public abstract class Address {
public abstract int getVersion(); public abstract int getVersion();
public abstract Script getOutputScript(); public abstract ScriptType getScriptType();
public abstract byte[] getOutputScriptData(); public abstract byte[] getOutputScriptData();
@ -42,9 +42,4 @@ public abstract class Address {
public int hashCode() { public int hashCode() {
return getAddress().hashCode(); return getAddress().hashCode();
} }
public String getScriptType() {
String className = this.getClass().getSimpleName();
return className.replace("Address", "");
}
} }

View file

@ -1,9 +1,7 @@
package com.sparrowwallet.drongo.address; package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.Script; import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.protocol.ScriptChunk;
import com.sparrowwallet.drongo.protocol.ScriptOpCodes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -20,12 +18,8 @@ public class P2PKAddress extends Address {
return 0; return 0;
} }
public Script getOutputScript() { public ScriptType getScriptType() {
List<ScriptChunk> chunks = new ArrayList<>(); return ScriptType.P2PK;
chunks.add(new ScriptChunk(pubKey.length, pubKey));
chunks.add(new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, null));
return new Script(chunks);
} }
@Override @Override

View file

@ -1,11 +1,6 @@
package com.sparrowwallet.drongo.address; package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.protocol.Script; import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.protocol.ScriptChunk;
import com.sparrowwallet.drongo.protocol.ScriptOpCodes;
import java.util.ArrayList;
import java.util.List;
public class P2PKHAddress extends Address { public class P2PKHAddress extends Address {
public P2PKHAddress(byte[] pubKeyHash) { public P2PKHAddress(byte[] pubKeyHash) {
@ -16,15 +11,8 @@ public class P2PKHAddress extends Address {
return 0; return 0;
} }
public Script getOutputScript() { public ScriptType getScriptType() {
List<ScriptChunk> chunks = new ArrayList<>(); return ScriptType.P2PKH;
chunks.add(new ScriptChunk(ScriptOpCodes.OP_DUP, null));
chunks.add(new ScriptChunk(ScriptOpCodes.OP_HASH160, null));
chunks.add(new ScriptChunk(hash.length, hash));
chunks.add(new ScriptChunk(ScriptOpCodes.OP_EQUALVERIFY, null));
chunks.add(new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, null));
return new Script(chunks);
} }
@Override @Override

View file

@ -1,12 +1,7 @@
package com.sparrowwallet.drongo.address; package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.Script; import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.protocol.ScriptChunk;
import com.sparrowwallet.drongo.protocol.ScriptOpCodes;
import java.util.ArrayList;
import java.util.List;
public class P2SHAddress extends Address { public class P2SHAddress extends Address {
public P2SHAddress(byte[] scriptHash) { public P2SHAddress(byte[] scriptHash) {
@ -17,13 +12,8 @@ public class P2SHAddress extends Address {
return 5; return 5;
} }
public Script getOutputScript() { public ScriptType getScriptType() {
List<ScriptChunk> chunks = new ArrayList<>(); return ScriptType.P2SH;
chunks.add(new ScriptChunk(ScriptOpCodes.OP_HASH160, null));
chunks.add(new ScriptChunk(hash.length, hash));
chunks.add(new ScriptChunk(ScriptOpCodes.OP_EQUAL, null));
return new Script(chunks);
} }
@Override @Override

View file

@ -1,11 +1,7 @@
package com.sparrowwallet.drongo.address; package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.protocol.Bech32; import com.sparrowwallet.drongo.protocol.Bech32;
import com.sparrowwallet.drongo.protocol.Script; import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.protocol.ScriptChunk;
import java.util.ArrayList;
import java.util.List;
public class P2WPKHAddress extends Address { public class P2WPKHAddress extends Address {
public static final String HRP = "bc"; public static final String HRP = "bc";
@ -22,12 +18,8 @@ public class P2WPKHAddress extends Address {
return Bech32.encode(HRP, getVersion(), hash); return Bech32.encode(HRP, getVersion(), hash);
} }
public Script getOutputScript() { public ScriptType getScriptType() {
List<ScriptChunk> chunks = new ArrayList<>(); return ScriptType.P2WPKH;
chunks.add(new ScriptChunk(Script.encodeToOpN(getVersion()), null));
chunks.add(new ScriptChunk(hash.length, hash));
return new Script(chunks);
} }
@Override @Override

View file

@ -2,9 +2,6 @@ package com.sparrowwallet.drongo.address;
import com.sparrowwallet.drongo.protocol.*; import com.sparrowwallet.drongo.protocol.*;
import java.util.ArrayList;
import java.util.List;
import static com.sparrowwallet.drongo.address.P2WPKHAddress.HRP; import static com.sparrowwallet.drongo.address.P2WPKHAddress.HRP;
public class P2WSHAddress extends Address { public class P2WSHAddress extends Address {
@ -20,12 +17,8 @@ public class P2WSHAddress extends Address {
return Bech32.encode(HRP, getVersion(), hash); return Bech32.encode(HRP, getVersion(), hash);
} }
public Script getOutputScript() { public ScriptType getScriptType() {
List<ScriptChunk> chunks = new ArrayList<>(); return ScriptType.P2WSH;
chunks.add(new ScriptChunk(Script.encodeToOpN(getVersion()), null));
chunks.add(new ScriptChunk(hash.length, hash));
return new Script(chunks);
} }
@Override @Override

View file

@ -1,204 +0,0 @@
package com.sparrowwallet.drongo.protocol;
import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.address.P2PKAddress;
import com.sparrowwallet.drongo.crypto.ECKey;
import java.util.ArrayList;
import java.util.List;
import static com.sparrowwallet.drongo.protocol.ScriptOpCodes.*;
import static com.sparrowwallet.drongo.protocol.Script.decodeFromOpN;
public class ScriptPattern {
/**
* Returns true if this script is of the form {@code DUP HASH160 <pubkey hash> EQUALVERIFY CHECKSIG}, ie, payment to an
* public key like {@code 2102f3b08938a7f8d2609d567aebc4989eeded6e2e880c058fdf092c5da82c3bc5eeac}.
*/
public static boolean isP2PK(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;
}
/**
* Extract the pubkey from a P2PK scriptPubKey. It's important that the script is in the correct form, so you
* will want to guard calls to this method with {@link #isP2PK(Script)}.
*/
public static ECKey extractPKFromP2PK(Script script) {
return ECKey.fromPublicOnly(script.chunks.get(0).data);
}
/**
* Returns true if this script is of the form {@code DUP HASH160 <pubkey hash> EQUALVERIFY CHECKSIG}, ie, payment to an
* address like {@code 1VayNert3x1KzbpzMGt2qdqrAThiRovi8}. This form was originally intended for the case where you wish
* to send somebody money with a written code because their node is offline, but over time has become the standard
* way to make payments due to the short and recognizable base58 form addresses come in.
*/
public static boolean isP2PKH(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;
}
/**
* Extract the pubkey hash from a P2PKH scriptPubKey. It's important that the script is in the correct form, so you
* will want to guard calls to this method with {@link #isP2PKH(Script)}.
*/
public static byte[] extractHashFromP2PKH(Script script) {
return script.chunks.get(2).data;
}
/**
* <p>
* Whether or not this is a scriptPubKey representing a P2SH output. In such outputs, the logic that
* controls reclamation is not actually in the output at all. Instead there's just a hash, and it's up to the
* spending input to provide a program matching that hash.
* </p>
* <p>
* P2SH is described by <a href="https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki">BIP16</a>.
* </p>
*/
public static boolean isP2SH(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;
}
/**
* Extract the script hash from a P2SH scriptPubKey. It's important that the script is in the correct form, so you
* will want to guard calls to this method with {@link #isP2SH(Script)}.
*/
public static byte[] extractHashFromP2SH(Script script) {
return script.chunks.get(1).data;
}
/**
* Returns whether this script matches the format used for multisig outputs:
* {@code [n] [keys...] [m] CHECKMULTISIG}
*/
public static boolean isMultisig(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 = 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 (decodeFromOpN(chunks.get(0).opcode) < 1) return false;
} catch (IllegalStateException e) {
return false; // Not an OP_N opcode.
}
return true;
}
public static int extractMultisigThreshold(Script script) {
return decodeFromOpN(script.chunks.get(0).opcode);
}
public static Address[] extractMultisigAddresses(Script script) {
List<Address> addresses = new ArrayList<>();
List<ScriptChunk> chunks = script.chunks;
for (int i = 1; i < chunks.size() - 2; i++) {
byte[] pubKey = chunks.get(i).data;
addresses.add(new P2PKAddress(pubKey));
}
return addresses.toArray(new Address[addresses.size()]);
}
/**
* Returns true if this script is of the form {@code OP_0 <hash[20]>}. This is a P2WPKH scriptPubKey.
*/
public static boolean isP2WPKH(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;
}
/**
* Returns true if this script is of the form {@code OP_0 <hash[32]>}. This is a P2WSH scriptPubKey.
*/
public static boolean isP2WSH(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;
}
/**
* Extract the pubkey hash from a P2WPKH or the script hash from a P2WSH scriptPubKey. It's important that the
* script is in the correct form, so you will want to guard calls to this method with
* {@link #isP2WPKH(Script)} or {@link #isP2WSH(Script)}.
*/
public static byte[] extractHashFromP2WH(Script script) {
return script.chunks.get(1).data;
}
}