From 188cf6081d677634c2e4fc8a83f9e9965db729d3 Mon Sep 17 00:00:00 2001 From: Jakob Stuber Date: Thu, 10 Apr 2014 22:48:41 -0500 Subject: [PATCH] Add support for creating multisig inputs scripts that redeem P2SH outputs --- .../google/bitcoin/script/ScriptBuilder.java | 25 +++++++-- .../com/google/bitcoin/script/ScriptTest.java | 51 +++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java b/core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java index aa83483d..6cdf2601 100644 --- a/core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java +++ b/core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java @@ -21,6 +21,7 @@ import com.google.bitcoin.core.ECKey; import com.google.bitcoin.crypto.TransactionSignature; import com.google.common.collect.Lists; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -116,10 +117,7 @@ public class ScriptBuilder { /** Create a program that satisfies an OP_CHECKMULTISIG program. */ public static Script createMultiSigInputScript(List signatures) { - List sigs = new ArrayList(signatures.size()); - for (TransactionSignature signature : signatures) - sigs.add(signature.encodeToBitcoin()); - return createMultiSigInputScriptBytes(sigs); + return createP2SHMultiSigInputScript(signatures, null); } /** Create a program that satisfies an OP_CHECKMULTISIG program. */ @@ -129,11 +127,30 @@ public class ScriptBuilder { /** Create a program that satisfies an OP_CHECKMULTISIG program, using pre-encoded signatures. */ public static Script createMultiSigInputScriptBytes(List signatures) { + return createMultiSigInputScriptBytes(signatures, null); + } + + /** Create a program that satisfies a pay-to-script hashed OP_CHECKMULTISIG program. */ + public static Script createP2SHMultiSigInputScript(List signatures, + byte[] multisigProgramBytes) { + List sigs = new ArrayList(signatures.size()); + for (TransactionSignature signature : signatures) + sigs.add(signature.encodeToBitcoin()); + return createMultiSigInputScriptBytes(sigs, multisigProgramBytes); + } + + /** + * Create a program that satisfies an OP_CHECKMULTISIG program, using pre-encoded signatures. + * Optionally, appends the script program bytes if spending a P2SH output. + */ + public static Script createMultiSigInputScriptBytes(List signatures, @Nullable byte[] multisigProgramBytes) { checkArgument(signatures.size() <= 16); ScriptBuilder builder = new ScriptBuilder(); builder.smallNum(0); // Work around a bug in CHECKMULTISIG that is now a required part of the protocol. for (byte[] signature : signatures) builder.data(signature); + if (multisigProgramBytes!= null) + builder.data(multisigProgramBytes); return builder.build(); } diff --git a/core/src/test/java/com/google/bitcoin/script/ScriptTest.java b/core/src/test/java/com/google/bitcoin/script/ScriptTest.java index 4d56b3cf..2eb3205b 100644 --- a/core/src/test/java/com/google/bitcoin/script/ScriptTest.java +++ b/core/src/test/java/com/google/bitcoin/script/ScriptTest.java @@ -18,10 +18,16 @@ package com.google.bitcoin.script; import com.google.bitcoin.core.*; +import com.google.bitcoin.core.Transaction.SigHash; +import com.google.bitcoin.crypto.TransactionSignature; import com.google.bitcoin.params.MainNetParams; import com.google.bitcoin.params.TestNet3Params; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import org.hamcrest.core.IsEqual; +import org.hamcrest.core.IsNot; +import org.junit.Assert; import org.junit.Test; import org.spongycastle.util.encoders.Hex; @@ -30,6 +36,7 @@ import java.io.InputStreamReader; import java.math.BigInteger; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -102,6 +109,50 @@ public class ScriptTest { assertTrue(s.isSentToRawPubKey()); } + @Test + public void testCreateMultiSigInputScript() throws AddressFormatException { + // Setup transaction and signatures + ECKey key1 = new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT").getKey(); + ECKey key2 = new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo").getKey(); + ECKey key3 = new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg").getKey(); + Script multisigScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(key1, key2, key3)); + byte[] bytes = Hex.decode("01000000013df681ff83b43b6585fa32dd0e12b0b502e6481e04ee52ff0fdaf55a16a4ef61000000006b483045022100a84acca7906c13c5895a1314c165d33621cdcf8696145080895cbf301119b7cf0220730ff511106aa0e0a8570ff00ee57d7a6f24e30f592a10cae1deffac9e13b990012102b8d567bcd6328fd48a429f9cf4b315b859a58fd28c5088ef3cb1d98125fc4e8dffffffff02364f1c00000000001976a91439a02793b418de8ec748dd75382656453dc99bcb88ac40420f000000000017a9145780b80be32e117f675d6e0ada13ba799bf248e98700000000"); + Transaction transaction = new Transaction(params, bytes); + TransactionOutput output = transaction.getOutput(1); + Transaction spendTx = new Transaction(params); + Address address = new Address(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.getProgram()); + + // 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(IsEqual.equalTo(multisigScript.getProgram()))); + } + private Script parseScriptString(String string) throws Exception { String[] words = string.split("[ \\t\\n]");