mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-02-24 14:50:57 +01:00
Script: Standard patterns are moved to a separate ScriptPattern class.
This commit is contained in:
parent
b6360e0858
commit
0ede3c7367
3 changed files with 163 additions and 50 deletions
|
@ -233,8 +233,7 @@ public class Script {
|
|||
* useful more exotic types of transaction, but today most payments are to addresses.
|
||||
*/
|
||||
public boolean isSentToRawPubKey() {
|
||||
return chunks.size() == 2 && chunks.get(1).equalsOpCode(OP_CHECKSIG) &&
|
||||
!chunks.get(0).isOpCode() && chunks.get(0).data.length > 1;
|
||||
return ScriptPattern.isSentToRawPubKey(chunks);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -244,12 +243,7 @@ public class Script {
|
|||
* way to make payments due to the short and recognizable base58 form addresses come in.
|
||||
*/
|
||||
public boolean isSentToAddress() {
|
||||
return chunks.size() == 5 &&
|
||||
chunks.get(0).equalsOpCode(OP_DUP) &&
|
||||
chunks.get(1).equalsOpCode(OP_HASH160) &&
|
||||
chunks.get(2).data.length == Address.LENGTH &&
|
||||
chunks.get(3).equalsOpCode(OP_EQUALVERIFY) &&
|
||||
chunks.get(4).equalsOpCode(OP_CHECKSIG);
|
||||
return ScriptPattern.isSentToAddress(chunks);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -687,56 +681,18 @@ public class Script {
|
|||
* Bitcoin system).</p>
|
||||
*/
|
||||
public boolean isPayToScriptHash() {
|
||||
// We have to check against the 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! :(
|
||||
byte[] program = getProgram();
|
||||
return program.length == 23 &&
|
||||
(program[0] & 0xff) == OP_HASH160 &&
|
||||
(program[1] & 0xff) == 0x14 &&
|
||||
(program[22] & 0xff) == OP_EQUAL;
|
||||
return ScriptPattern.isPayToScriptHash(chunks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this script matches the format used for multisig outputs: [n] [keys...] [m] CHECKMULTISIG
|
||||
*/
|
||||
public boolean isSentToMultiSig() {
|
||||
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 (IllegalArgumentException e) { // thrown by decodeFromOpN()
|
||||
return false; // Not an OP_N opcode.
|
||||
}
|
||||
return true;
|
||||
return ScriptPattern.isSentToMultisig(chunks);
|
||||
}
|
||||
|
||||
public boolean isSentToCLTVPaymentChannel() {
|
||||
if (chunks.size() != 10) return false;
|
||||
// Check that opcodes match the pre-determined format.
|
||||
if (!chunks.get(0).equalsOpCode(OP_IF)) return false;
|
||||
// chunk[1] = recipient pubkey
|
||||
if (!chunks.get(2).equalsOpCode(OP_CHECKSIGVERIFY)) return false;
|
||||
if (!chunks.get(3).equalsOpCode(OP_ELSE)) return false;
|
||||
// chunk[4] = locktime
|
||||
if (!chunks.get(5).equalsOpCode(OP_CHECKLOCKTIMEVERIFY)) return false;
|
||||
if (!chunks.get(6).equalsOpCode(OP_DROP)) return false;
|
||||
if (!chunks.get(7).equalsOpCode(OP_ENDIF)) return false;
|
||||
// chunk[8] = sender pubkey
|
||||
if (!chunks.get(9).equalsOpCode(OP_CHECKSIG)) return false;
|
||||
return true;
|
||||
return ScriptPattern.isSentToCltvPaymentChannel(chunks);
|
||||
}
|
||||
|
||||
private static boolean equalsRange(byte[] a, int start, byte[] b) {
|
||||
|
@ -853,7 +809,7 @@ public class Script {
|
|||
}
|
||||
|
||||
public boolean isOpReturn() {
|
||||
return chunks.size() > 0 && chunks.get(0).equalsOpCode(OP_RETURN);
|
||||
return ScriptPattern.isOpReturn(chunks);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
104
core/src/main/java/org/bitcoinj/script/ScriptPattern.java
Normal file
104
core/src/main/java/org/bitcoinj/script/ScriptPattern.java
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 2017 John L. Jegutanis
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bitcoinj.script;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.bitcoinj.script.Script.decodeFromOpN;
|
||||
import static org.bitcoinj.script.ScriptOpCodes.*;
|
||||
|
||||
/**
|
||||
* This is a Script pattern matcher with some typical script patterns
|
||||
*/
|
||||
public class ScriptPattern {
|
||||
public static boolean isSentToAddress(List<ScriptChunk> chunks) {
|
||||
return chunks.size() == 5 &&
|
||||
chunks.get(0).equalsOpCode(OP_DUP) &&
|
||||
chunks.get(1).equalsOpCode(OP_HASH160) &&
|
||||
chunks.get(2).data != null &&
|
||||
chunks.get(2).data.length == Address.LENGTH &&
|
||||
chunks.get(3).equalsOpCode(OP_EQUALVERIFY) &&
|
||||
chunks.get(4).equalsOpCode(OP_CHECKSIG);
|
||||
}
|
||||
|
||||
public static boolean isPayToScriptHash(List<ScriptChunk> 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 OP_HASH160 OP_PUSHDATA1 0x14 <20 bytes of script hash> OP_EQUAL
|
||||
return chunks.size() == 3 &&
|
||||
chunks.get(0).equalsOpCode(OP_HASH160) &&
|
||||
chunks.get(1).opcode == 0x14 &&
|
||||
chunks.get(1).data != null &&
|
||||
chunks.get(1).data.length == Address.LENGTH &&
|
||||
chunks.get(2).equalsOpCode(OP_EQUAL);
|
||||
}
|
||||
|
||||
public static boolean isSentToRawPubKey(List<ScriptChunk> chunks) {
|
||||
return chunks.size() == 2 &&
|
||||
chunks.get(1).equalsOpCode(OP_CHECKSIG) &&
|
||||
!chunks.get(0).isOpCode() &&
|
||||
chunks.get(0).data != null &&
|
||||
chunks.get(0).data.length > 1;
|
||||
}
|
||||
|
||||
public static boolean isSentToMultisig(List<ScriptChunk> 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 boolean isSentToCltvPaymentChannel(List<ScriptChunk> chunks) {
|
||||
if (chunks.size() != 10) return false;
|
||||
// Check that opcodes match the pre-determined format.
|
||||
if (!chunks.get(0).equalsOpCode(OP_IF)) return false;
|
||||
// chunk[1] = recipient pubkey
|
||||
if (!chunks.get(2).equalsOpCode(OP_CHECKSIGVERIFY)) return false;
|
||||
if (!chunks.get(3).equalsOpCode(OP_ELSE)) return false;
|
||||
// chunk[4] = locktime
|
||||
if (!chunks.get(5).equalsOpCode(OP_CHECKLOCKTIMEVERIFY)) return false;
|
||||
if (!chunks.get(6).equalsOpCode(OP_DROP)) return false;
|
||||
if (!chunks.get(7).equalsOpCode(OP_ENDIF)) return false;
|
||||
// chunk[8] = sender pubkey
|
||||
if (!chunks.get(9).equalsOpCode(OP_CHECKSIG)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isOpReturn(List<ScriptChunk> chunks) {
|
||||
return chunks.size() > 0 && chunks.get(0).equalsOpCode(ScriptOpCodes.OP_RETURN);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2017 John L. Jegutanis
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bitcoinj.script;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.params.MainNetParams;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ScriptPatternTest {
|
||||
private List<ECKey> keys = Lists.newArrayList(new ECKey(), new ECKey(), new ECKey());
|
||||
|
||||
@Test
|
||||
public void testCommonScripts() {
|
||||
assertTrue(ScriptPattern.isSentToAddress(
|
||||
ScriptBuilder.createOutputScript(keys.get(0).toAddress(MainNetParams.get())).getChunks()
|
||||
));
|
||||
assertTrue(ScriptPattern.isPayToScriptHash(
|
||||
ScriptBuilder.createP2SHOutputScript(2, keys).getChunks()
|
||||
));
|
||||
assertTrue(ScriptPattern.isSentToMultisig(
|
||||
ScriptBuilder.createMultiSigOutputScript(2, keys).getChunks()
|
||||
));
|
||||
assertTrue(ScriptPattern.isSentToRawPubKey(
|
||||
ScriptBuilder.createOutputScript(keys.get(0)).getChunks()
|
||||
));
|
||||
assertTrue(ScriptPattern.isSentToCltvPaymentChannel(
|
||||
ScriptBuilder.createCLTVPaymentChannelOutput(BigInteger.ONE, keys.get(0), keys.get(1)).getChunks()
|
||||
));
|
||||
assertTrue(ScriptPattern.isOpReturn(
|
||||
ScriptBuilder.createOpReturnScript(new byte[10]).getChunks()
|
||||
));
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue