/*
* Copyright 2011 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* 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.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.bitcoinj.core.*;
import org.bitcoinj.core.Transaction.SigHash;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.script.Script.VerifyFlag;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.hamcrest.core.IsNot;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.*;
import static org.bitcoinj.core.Utils.HEX;
import static org.bitcoinj.script.ScriptOpCodes.OP_0;
import static org.bitcoinj.script.ScriptOpCodes.OP_INVALIDOPCODE;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.*;
import org.junit.Before;
public class ScriptTest {
// From tx 05e04c26c12fe408a3c1b71aa7996403f6acad1045252b1c62e055496f4d2cb1 on the testnet.
static final String sigProg = "47304402202b4da291cc39faf8433911988f9f49fc5c995812ca2f94db61468839c228c3e90220628bff3ff32ec95825092fa051cba28558a981fcf59ce184b14f2e215e69106701410414b38f4be3bb9fa0f4f32b74af07152b2f2f630bc02122a491137b6c523e46f18a0d5034418966f93dfc37cc3739ef7b2007213a302b7fba161557f4ad644a1c";
static final String pubkeyProg = "76a91433e81a941e64cda12c6a299ed322ddbdd03f8d0e88ac";
private static final NetworkParameters PARAMS = TestNet3Params.get();
private static final Logger log = LoggerFactory.getLogger(ScriptTest.class);
@Before
public void setUp() throws Exception {
Context context = new Context(PARAMS);
}
@Test
public void testScriptSig() throws Exception {
byte[] sigProgBytes = HEX.decode(sigProg);
Script script = new Script(sigProgBytes);
// Test we can extract the from address.
byte[] hash160 = Utils.sha256hash160(script.getPubKey());
Address a = new Address(PARAMS, hash160);
assertEquals("mkFQohBpy2HDXrCwyMrYL5RtfrmeiuuPY2", a.toString());
}
@Test
public void testScriptPubKey() throws Exception {
// Check we can extract the to address
byte[] pubkeyBytes = HEX.decode(pubkeyProg);
Script pubkey = new Script(pubkeyBytes);
assertEquals("DUP HASH160 PUSHDATA(20)[33e81a941e64cda12c6a299ed322ddbdd03f8d0e] EQUALVERIFY CHECKSIG", pubkey.toString());
Address toAddr = new Address(PARAMS, pubkey.getPubKeyHash());
assertEquals("mkFQohBpy2HDXrCwyMrYL5RtfrmeiuuPY2", toAddr.toString());
}
@Test
public void testMultiSig() throws Exception {
List<ECKey> keys = Lists.newArrayList(new ECKey(), new ECKey(), new ECKey());
assertTrue(ScriptBuilder.createMultiSigOutputScript(2, keys).isSentToMultiSig());
Script script = ScriptBuilder.createMultiSigOutputScript(3, keys);
assertTrue(script.isSentToMultiSig());
List<ECKey> pubkeys = new ArrayList<ECKey>(3);
for (ECKey key : keys) pubkeys.add(ECKey.fromPublicOnly(key.getPubKeyPoint()));
assertEquals(script.getPubKeys(), pubkeys);
assertFalse(ScriptBuilder.createOutputScript(new ECKey()).isSentToMultiSig());
try {
// Fail if we ask for more signatures than keys.
Script.createMultiSigOutputScript(4, keys);
fail();
} catch (Throwable e) {
// Expected.
}
try {
// Must have at least one signature required.
Script.createMultiSigOutputScript(0, keys);
} catch (Throwable e) {
// Expected.
}
// Actual execution is tested by the data driven tests.
}
@Test
public void testP2SHOutputScript() throws Exception {
Address p2shAddress = Address.fromBase58(MainNetParams.get(), "35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU");
assertTrue(ScriptBuilder.createOutputScript(p2shAddress).isPayToScriptHash());
}
@Test
public void testIp() throws Exception {
byte[] bytes = HEX.decode("41043e96222332ea7848323c08116dddafbfa917b8e37f0bdf63841628267148588a09a43540942d58d49717ad3fabfe14978cf4f0a8b84d2435dad16e9aa4d7f935ac");
Script s = new Script(bytes);
assertTrue(s.isSentToRawPubKey());
}
@Test
public void testCreateMultiSigInputScript() {
// Setup transaction and signatures
ECKey key1 = DumpedPrivateKey.fromBase58(PARAMS, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT").getKey();
ECKey key2 = DumpedPrivateKey.fromBase58(PARAMS, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo").getKey();
ECKey key3 = DumpedPrivateKey.fromBase58(PARAMS, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg").getKey();
Script multisigScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(key1, key2, key3));
byte[] bytes = HEX.decode("01000000013df681ff83b43b6585fa32dd0e12b0b502e6481e04ee52ff0fdaf55a16a4ef61000000006b483045022100a84acca7906c13c5895a1314c165d33621cdcf8696145080895cbf301119b7cf0220730ff511106aa0e0a8570ff00ee57d7a6f24e30f592a10cae1deffac9e13b990012102b8d567bcd6328fd48a429f9cf4b315b859a58fd28c5088ef3cb1d98125fc4e8dffffffff02364f1c00000000001976a91439a02793b418de8ec748dd75382656453dc99bcb88ac40420f000000000017a9145780b80be32e117f675d6e0ada13ba799bf248e98700000000");
Transaction transaction = PARAMS.getDefaultSerializer().makeTransaction(bytes);
TransactionOutput output = transaction.getOutput(1);
Transaction spendTx = new Transaction(PARAMS);
Address address = Address.fromBase58(PARAMS, "n3CFiCmBXVt5d3HXKQ15EFZyhPz4yj5F3H");
Script outputScript = ScriptBuilder.createOutputScript(address);
spendTx.addOutput(output.getValue(), outputScript);
spendTx.addInput(output);
Sha256Hash sighash = spendTx.hashForSignature(0, multisigScript, SigHash.ALL, false);
ECKey.ECDSASignature party1Signature = key1.sign(sighash);
ECKey.ECDSASignature party2Signature = key2.sign(sighash);
TransactionSignature party1TransactionSignature = new TransactionSignature(party1Signature, SigHash.ALL, false);
TransactionSignature party2TransactionSignature = new TransactionSignature(party2Signature, SigHash.ALL, false);
// Create p2sh multisig input script
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(party1TransactionSignature, party2TransactionSignature), multisigScript);
// Assert that the input script contains 4 chunks
assertTrue(inputScript.getChunks().size() == 4);
// Assert that the input script created contains the original multisig
// script as the last chunk
ScriptChunk scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1);
Assert.assertArrayEquals(scriptChunk.data, multisigScript.getProgram());
// Create regular multisig input script
inputScript = ScriptBuilder.createMultiSigInputScript(ImmutableList.of(party1TransactionSignature, party2TransactionSignature));
// Assert that the input script only contains 3 chunks
assertTrue(inputScript.getChunks().size() == 3);
// Assert that the input script created does not end with the original
// multisig script
scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1);
Assert.assertThat(scriptChunk.data, IsNot.not(equalTo(multisigScript.getProgram())));
}
@Test
public void createAndUpdateEmptyInputScript() throws Exception {
TransactionSignature dummySig = TransactionSignature.dummy();
ECKey key = new ECKey();
// pay-to-pubkey
Script inputScript = ScriptBuilder.createInputScript(dummySig);
assertThat(inputScript.getChunks().get(0).data, equalTo(dummySig.encodeToBitcoin()));
inputScript = ScriptBuilder.createInputScript(null);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
// pay-to-address
inputScript = ScriptBuilder.createInputScript(dummySig, key);
assertThat(inputScript.getChunks().get(0).data, equalTo(dummySig.encodeToBitcoin()));
inputScript = ScriptBuilder.createInputScript(null, key);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(1).data, equalTo(key.getPubKey()));
// pay-to-script-hash
ECKey key2 = new ECKey();
Script multisigScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(key, key2));
inputScript = ScriptBuilder.createP2SHMultiSigInputScript(Arrays.asList(dummySig, dummySig), multisigScript);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(1).data, equalTo(dummySig.encodeToBitcoin()));
assertThat(inputScript.getChunks().get(2).data, equalTo(dummySig.encodeToBitcoin()));
assertThat(inputScript.getChunks().get(3).data, equalTo(multisigScript.getProgram()));
inputScript = ScriptBuilder.createP2SHMultiSigInputScript(null, multisigScript);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(1).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(2).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(3).data, equalTo(multisigScript.getProgram()));
inputScript = ScriptBuilder.updateScriptWithSignature(inputScript, dummySig.encodeToBitcoin(), 0, 1, 1);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(1).data, equalTo(dummySig.encodeToBitcoin()));
assertThat(inputScript.getChunks().get(2).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(3).data, equalTo(multisigScript.getProgram()));
inputScript = ScriptBuilder.updateScriptWithSignature(inputScript, dummySig.encodeToBitcoin(), 1, 1, 1);
assertThat(inputScript.getChunks().get(0).opcode, equalTo(OP_0));
assertThat(inputScript.getChunks().get(1).data, equalTo(dummySig.encodeToBitcoin()));
assertThat(inputScript.getChunks().get(2).data, equalTo(dummySig.encodeToBitcoin()));
assertThat(inputScript.getChunks().get(3).data, equalTo(multisigScript.getProgram()));
// updating scriptSig with no missing signatures
try {
ScriptBuilder.updateScriptWithSignature(inputScript, dummySig.encodeToBitcoin(), 1, 1, 1);
fail("Exception expected");
} catch (Exception e) {
assertEquals(IllegalArgumentException.class, e.getClass());
}
}
@Test
public void testOp0() {
// Check that OP_0 doesn't NPE and pushes an empty stack frame.
Transaction tx = new Transaction(PARAMS);
tx.addInput(new TransactionInput(PARAMS, tx, new byte[] {}));
Script script = new ScriptBuilder().smallNum(0).build();
LinkedList<byte[]> stack = new LinkedList<byte[]>();
Script.executeScript(tx, 0, script, stack, Script.ALL_VERIFY_FLAGS);
assertEquals("OP_0 push length", 0, stack.get(0).length);
}
private Script parseScriptString(String string) throws IOException {
String[] words = string.split("[ \\t\\n]");
UnsafeByteArrayOutputStream out = new UnsafeByteArrayOutputStream();
for(String w : words) {
if (w.equals(""))
continue;
if (w.matches("^-?[0-9]*$")) {
// Number
long val = Long.parseLong(w);
if (val >= -1 && val <= 16)
out.write(Script.encodeToOpN((int)val));
else
Script.writeBytes(out, Utils.reverseBytes(Utils.encodeMPI(BigInteger.valueOf(val), false)));
} else if (w.matches("^0x[0-9a-fA-F]*$")) {
// Raw hex data, inserted NOT pushed onto stack:
out.write(HEX.decode(w.substring(2).toLowerCase()));
} else if (w.length() >= 2 && w.startsWith("'") && w.endsWith("'")) {
// Single-quoted string, pushed as data. NOTE: this is poor-man's
// parsing, spaces/tabs/newlines in single-quoted strings won't work.
Script.writeBytes(out, w.substring(1, w.length() - 1).getBytes(Charset.forName("UTF-8")));
} else if (ScriptOpCodes.getOpCode(w) != OP_INVALIDOPCODE) {
// opcode, e.g. OP_ADD or OP_1:
out.write(ScriptOpCodes.getOpCode(w));
} else if (w.startsWith("OP_") && ScriptOpCodes.getOpCode(w.substring(3)) != OP_INVALIDOPCODE) {
// opcode, e.g. OP_ADD or OP_1:
out.write(ScriptOpCodes.getOpCode(w.substring(3)));
} else {
throw new RuntimeException("Invalid Data");
}
}
return new Script(out.toByteArray());
}
private Set<VerifyFlag> parseVerifyFlags(String str) {
Set<VerifyFlag> flags = EnumSet.noneOf(VerifyFlag.class);
if (!"NONE".equals(str)) {
for (String flag : str.split(",")) {
try {
flags.add(VerifyFlag.valueOf(flag));
} catch (IllegalArgumentException x) {
log.debug("Cannot handle verify flag {} -- ignored.", flag);
}
}
}
return flags;
}
@Test
public void dataDrivenValidScripts() throws Exception {
JsonNode json = new ObjectMapper().readTree(new InputStreamReader(getClass().getResourceAsStream(
"script_valid.json"), Charsets.UTF_8));
for (JsonNode test : json) {
Script scriptSig = parseScriptString(test.get(0).asText());
Script scriptPubKey = parseScriptString(test.get(1).asText());
Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
try {
scriptSig.correctlySpends(new Transaction(PARAMS), 0, scriptPubKey, verifyFlags);
} catch (ScriptException e) {
System.err.println(test);
System.err.flush();
throw e;
}
}
}
@Test
public void dataDrivenInvalidScripts() throws Exception {
JsonNode json = new ObjectMapper().readTree(new InputStreamReader(getClass().getResourceAsStream(
"script_invalid.json"), Charsets.UTF_8));
for (JsonNode test : json) {
try {
Script scriptSig = parseScriptString(test.get(0).asText());
Script scriptPubKey = parseScriptString(test.get(1).asText());
Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
scriptSig.correctlySpends(new Transaction(PARAMS), 0, scriptPubKey, verifyFlags);
System.err.println(test);
System.err.flush();
fail();
} catch (VerificationException e) {
// Expected.
}
}
}
private Map<TransactionOutPoint, Script> parseScriptPubKeys(JsonNode inputs) throws IOException {
Map<TransactionOutPoint, Script> scriptPubKeys = new HashMap<TransactionOutPoint, Script>();
for (JsonNode input : inputs) {
String hash = input.get(0).asText();
int index = input.get(1).asInt();
String script = input.get(2).asText();
Sha256Hash sha256Hash = Sha256Hash.wrap(HEX.decode(hash));
scriptPubKeys.put(new TransactionOutPoint(PARAMS, index, sha256Hash), parseScriptString(script));
}
return scriptPubKeys;
}
@Test
public void dataDrivenValidTransactions() throws Exception {
JsonNode json = new ObjectMapper().readTree(new InputStreamReader(getClass().getResourceAsStream(
"tx_valid.json"), Charsets.UTF_8));
for (JsonNode test : json) {
if (test.isArray() && test.size() == 1 && test.get(0).isTextual())
continue; // This is a comment.
Transaction transaction = null;
try {
Map<TransactionOutPoint, Script> scriptPubKeys = parseScriptPubKeys(test.get(0));
transaction = PARAMS.getDefaultSerializer().makeTransaction(HEX.decode(test.get(1).asText().toLowerCase()));
transaction.verify();
Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
for (int i = 0; i < transaction.getInputs().size(); i++) {
TransactionInput input = transaction.getInputs().get(i);
if (input.getOutpoint().getIndex() == 0xffffffffL)
input.getOutpoint().setIndex(-1);
assertTrue(scriptPubKeys.containsKey(input.getOutpoint()));
input.getScriptSig().correctlySpends(transaction, i, scriptPubKeys.get(input.getOutpoint()),
verifyFlags);
}
} catch (Exception e) {
System.err.println(test);
if (transaction != null)
System.err.println(transaction);
throw e;
}
}
}
@Test
public void dataDrivenInvalidTransactions() throws Exception {
JsonNode json = new ObjectMapper().readTree(new InputStreamReader(getClass().getResourceAsStream(
"tx_invalid.json"), Charsets.UTF_8));
for (JsonNode test : json) {
if (test.isArray() && test.size() == 1 && test.get(0).isTextual())
continue; // This is a comment.
Map<TransactionOutPoint, Script> scriptPubKeys = parseScriptPubKeys(test.get(0));
Transaction transaction = PARAMS.getDefaultSerializer().makeTransaction(HEX.decode(test.get(1).asText().toLowerCase()));
Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
boolean valid = true;
try {
transaction.verify();
} catch (VerificationException e) {
valid = false;
}
// Bitcoin Core checks this case in CheckTransaction, but we leave it to
// later where we will see an attempt to double-spend, so we explicitly check here
HashSet<TransactionOutPoint> set = new HashSet<TransactionOutPoint>();
for (TransactionInput input : transaction.getInputs()) {
if (set.contains(input.getOutpoint()))
valid = false;
set.add(input.getOutpoint());
}
for (int i = 0; i < transaction.getInputs().size() && valid; i++) {
TransactionInput input = transaction.getInputs().get(i);
assertTrue(scriptPubKeys.containsKey(input.getOutpoint()));
try {
input.getScriptSig().correctlySpends(transaction, i, scriptPubKeys.get(input.getOutpoint()),
verifyFlags);
} catch (VerificationException e) {
valid = false;
}
}
if (valid)
fail();
}
}
@Test
public void testCLTVPaymentChannelOutput() {
Script script = ScriptBuilder.createCLTVPaymentChannelOutput(BigInteger.valueOf(20), new ECKey(), new ECKey());
assertTrue("script is locktime-verify", script.isSentToCLTVPaymentChannel());
}
@Test
public void getToAddress() throws Exception {
// pay to pubkey
ECKey toKey = new ECKey();
Address toAddress = toKey.toAddress(PARAMS);
assertEquals(toAddress, ScriptBuilder.createOutputScript(toKey).getToAddress(PARAMS, true));
// pay to pubkey hash
assertEquals(toAddress, ScriptBuilder.createOutputScript(toAddress).getToAddress(PARAMS, true));
// pay to script hash
Script p2shScript = ScriptBuilder.createP2SHOutputScript(new byte[20]);
Address scriptAddress = Address.fromP2SHScript(PARAMS, p2shScript);
assertEquals(scriptAddress, p2shScript.getToAddress(PARAMS, true));
}
@Test(expected = ScriptException.class)
public void getToAddressNoPubKey() throws Exception {
ScriptBuilder.createOutputScript(new ECKey()).getToAddress(PARAMS, false);
}
/** Test encoding of zero, which should result in an opcode */
@Test
public void numberBuilderZero() {
final ScriptBuilder builder = new ScriptBuilder();
// 0 should encode directly to 0
builder.number(0);
assertArrayEquals(new byte[] {
0x00 // Pushed data
}, builder.build().getProgram());
}
@Test
public void numberBuilderPositiveOpCode() {
final ScriptBuilder builder = new ScriptBuilder();
builder.number(5);
assertArrayEquals(new byte[] {
0x55 // Pushed data
}, builder.build().getProgram());
}
@Test
public void numberBuilderBigNum() {
ScriptBuilder builder = new ScriptBuilder();
// 21066 should take up three bytes including the length byte
// at the start
builder.number(0x524a);
assertArrayEquals(new byte[] {
0x02, // Length of the pushed data
0x4a, 0x52 // Pushed data
}, builder.build().getProgram());
// Test the trimming code ignores zeroes in the middle
builder = new ScriptBuilder();
builder.number(0x110011);
assertEquals(4, builder.build().getProgram().length);
// Check encoding of a value where signed/unsigned encoding differs
// because the most significant byte is 0x80, and therefore a
// sign byte has to be added to the end for the signed encoding.
builder = new ScriptBuilder();
builder.number(0x8000);
assertArrayEquals(new byte[] {
0x03, // Length of the pushed data
0x00, (byte) 0x80, 0x00 // Pushed data
}, builder.build().getProgram());
}
@Test
public void numberBuilderNegative() {
// Check encoding of a negative value
final ScriptBuilder builder = new ScriptBuilder();
builder.number(-5);
assertArrayEquals(new byte[] {
0x01, // Length of the pushed data
((byte) 133) // Pushed data
}, builder.build().getProgram());
}
}