mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-02-24 22:58:32 +01:00
Add ability to create scriptSigs with empty signatures
For partial scriptSig OP_0 is used as a signature placeholder: Pay-to-address: OP_0 [pubkey] Pay-to-pubkey: OP_0 P2SH with 2-of-3 CHECKMULTISIG: OP_0 OP_0 OP_0 [redeem script]
This commit is contained in:
parent
bc97cf977f
commit
5cfdca1c98
3 changed files with 119 additions and 32 deletions
|
@ -367,7 +367,28 @@ public class Script {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an incomplete scriptSig that, once filled with signatures, can redeem output containing this scriptPubKey.
|
||||
* Instead of the signatures resulting script has OP_0.
|
||||
* Having incomplete input script allows to pass around partially signed tx.
|
||||
* It is expected that this program later on will be updated with proper signatures.
|
||||
*/
|
||||
public Script createEmptyInputScript(@Nullable ECKey key, @Nullable Script redeemScript) {
|
||||
if (isSentToAddress()) {
|
||||
checkArgument(key != null, "Key required to create pay-to-address input script");
|
||||
return ScriptBuilder.createInputScript(null, key);
|
||||
} else if (isSentToRawPubKey()) {
|
||||
return ScriptBuilder.createInputScript(null);
|
||||
} else if (isPayToScriptHash()) {
|
||||
checkArgument(redeemScript != null, "Redeem script required to create P2SH input script");
|
||||
return ScriptBuilder.createP2SHMultiSigInputScript(null, redeemScript);
|
||||
} else {
|
||||
throw new ScriptException("Do not understand script type: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////// Interface used during verification of transactions/blocks ////////////////////////////////
|
||||
|
||||
private static int getSigOpCount(List<ScriptChunk> chunks, boolean accurate) throws ScriptException {
|
||||
|
@ -448,6 +469,24 @@ public class Script {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of signatures required to satisfy this script.
|
||||
*/
|
||||
public int getNumberOfSignaturesRequiredToSpend() {
|
||||
if (isSentToMultiSig()) {
|
||||
// for N of M CHECKMULTISIG script we will need N signatures to spend
|
||||
ScriptChunk nChunk = chunks.get(0);
|
||||
return Script.decodeFromOpN(nChunk.opcode);
|
||||
} else if (isSentToAddress() || isSentToRawPubKey()) {
|
||||
// pay-to-address and pay-to-pubkey require single sig
|
||||
return 1;
|
||||
} else if (isPayToScriptHash()) {
|
||||
throw new IllegalStateException("For P2SH number of signatures depends on redeem script");
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported script type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of bytes required to spend this script. It accepts optional ECKey and redeemScript that may
|
||||
* be required for certain types of script to estimate target size.
|
||||
|
@ -456,16 +495,10 @@ public class Script {
|
|||
if (isPayToScriptHash()) {
|
||||
// scriptSig: <sig> [sig] [sig...] <redeemscript>
|
||||
checkArgument(redeemScript != null, "P2SH script requires redeemScript to be spent");
|
||||
// for N of M CHECKMULTISIG redeem script we will need N signatures to spend
|
||||
ScriptChunk nChunk = redeemScript.getChunks().get(0);
|
||||
int n = Script.decodeFromOpN(nChunk.opcode);
|
||||
return n * SIG_SIZE + redeemScript.getProgram().length;
|
||||
return redeemScript.getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + redeemScript.getProgram().length;
|
||||
} else if (isSentToMultiSig()) {
|
||||
// scriptSig: OP_0 <sig> [sig] [sig...]
|
||||
// for N of M CHECKMULTISIG script we will need N signatures to spend
|
||||
ScriptChunk nChunk = chunks.get(0);
|
||||
int n = Script.decodeFromOpN(nChunk.opcode);
|
||||
return n * SIG_SIZE + 1;
|
||||
return getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + 1;
|
||||
} else if (isSentToRawPubKey()) {
|
||||
// scriptSig: <sig>
|
||||
return SIG_SIZE;
|
||||
|
|
|
@ -111,15 +111,23 @@ public class ScriptBuilder {
|
|||
return new ScriptBuilder().data(key.getPubKey()).op(OP_CHECKSIG).build();
|
||||
}
|
||||
|
||||
/** Creates a scriptSig that can redeem a pay-to-address output. */
|
||||
public static Script createInputScript(TransactionSignature signature, ECKey pubKey) {
|
||||
/**
|
||||
* Creates a scriptSig that can redeem a pay-to-address output.
|
||||
* If given signature is null, incomplete scriptSig will be created with OP_0 instead of signature
|
||||
*/
|
||||
public static Script createInputScript(@Nullable TransactionSignature signature, ECKey pubKey) {
|
||||
byte[] pubkeyBytes = pubKey.getPubKey();
|
||||
return new ScriptBuilder().data(signature.encodeToBitcoin()).data(pubkeyBytes).build();
|
||||
byte[] sigBytes = signature != null ? signature.encodeToBitcoin() : new byte[]{};
|
||||
return new ScriptBuilder().data(sigBytes).data(pubkeyBytes).build();
|
||||
}
|
||||
|
||||
/** Creates a scriptSig that can redeem a pay-to-pubkey output. */
|
||||
public static Script createInputScript(TransactionSignature signature) {
|
||||
return new ScriptBuilder().data(signature.encodeToBitcoin()).build();
|
||||
/**
|
||||
* Creates a scriptSig that can redeem a pay-to-pubkey output.
|
||||
* If given signature is null, incomplete scriptSig will be created with OP_0 instead of signature
|
||||
*/
|
||||
public static Script createInputScript(@Nullable TransactionSignature signature) {
|
||||
byte[] sigBytes = signature != null ? signature.encodeToBitcoin() : new byte[]{};
|
||||
return new ScriptBuilder().data(sigBytes).build();
|
||||
}
|
||||
|
||||
/** Creates a program that requires at least N of the given keys to sign, using OP_CHECKMULTISIG. */
|
||||
|
@ -139,7 +147,12 @@ public class ScriptBuilder {
|
|||
|
||||
/** Create a program that satisfies an OP_CHECKMULTISIG program. */
|
||||
public static Script createMultiSigInputScript(List<TransactionSignature> signatures) {
|
||||
return createP2SHMultiSigInputScript(signatures, null);
|
||||
List<byte[]> sigs = new ArrayList<byte[]>(signatures.size());
|
||||
for (TransactionSignature signature : signatures) {
|
||||
sigs.add(signature.encodeToBitcoin());
|
||||
}
|
||||
|
||||
return createMultiSigInputScriptBytes(sigs, null);
|
||||
}
|
||||
|
||||
/** Create a program that satisfies an OP_CHECKMULTISIG program. */
|
||||
|
@ -152,13 +165,24 @@ public class ScriptBuilder {
|
|||
return createMultiSigInputScriptBytes(signatures, null);
|
||||
}
|
||||
|
||||
/** Create a program that satisfies a pay-to-script hashed OP_CHECKMULTISIG program. */
|
||||
public static Script createP2SHMultiSigInputScript(List<TransactionSignature> signatures,
|
||||
byte[] multisigProgramBytes) {
|
||||
List<byte[]> sigs = new ArrayList<byte[]>(signatures.size());
|
||||
for (TransactionSignature signature : signatures)
|
||||
sigs.add(signature.encodeToBitcoin());
|
||||
return createMultiSigInputScriptBytes(sigs, multisigProgramBytes);
|
||||
/**
|
||||
* Create a program that satisfies a pay-to-script hashed OP_CHECKMULTISIG program.
|
||||
* If given signature list is null, incomplete scriptSig will be created with OP_0 instead of signatures
|
||||
*/
|
||||
public static Script createP2SHMultiSigInputScript(@Nullable List<TransactionSignature> signatures,
|
||||
Script multisigProgram) {
|
||||
List<byte[]> sigs = new ArrayList<byte[]>();
|
||||
if (signatures == null) {
|
||||
// create correct number of empty signatures
|
||||
int numSigs = multisigProgram.getNumberOfSignaturesRequiredToSpend();
|
||||
for (int i = 0; i < numSigs; i++)
|
||||
sigs.add(new byte[]{});
|
||||
} else {
|
||||
for (TransactionSignature signature : signatures) {
|
||||
sigs.add(signature.encodeToBitcoin());
|
||||
}
|
||||
}
|
||||
return createMultiSigInputScriptBytes(sigs, multisigProgram.getProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,8 +24,6 @@ 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;
|
||||
|
@ -34,14 +32,12 @@ import java.io.BufferedReader;
|
|||
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;
|
||||
import java.util.*;
|
||||
|
||||
import static com.google.bitcoin.core.Utils.HEX;
|
||||
import static com.google.bitcoin.script.ScriptOpCodes.OP_0;
|
||||
import static com.google.bitcoin.script.ScriptOpCodes.OP_INVALIDOPCODE;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ScriptTest {
|
||||
|
@ -131,7 +127,7 @@ public class ScriptTest {
|
|||
TransactionSignature party2TransactionSignature = new TransactionSignature(party2Signature, SigHash.ALL, false);
|
||||
|
||||
// Create p2sh multisig input script
|
||||
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(party1TransactionSignature, party2TransactionSignature), multisigScript.getProgram());
|
||||
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(party1TransactionSignature, party2TransactionSignature), multisigScript);
|
||||
|
||||
// Assert that the input script contains 4 chunks
|
||||
assertTrue(inputScript.getChunks().size() == 4);
|
||||
|
@ -150,7 +146,41 @@ public class ScriptTest {
|
|||
// 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())));
|
||||
Assert.assertThat(scriptChunk.data, IsNot.not(equalTo(multisigScript.getProgram())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateEmptyInputScript() 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()));
|
||||
}
|
||||
|
||||
private Script parseScriptString(String string) throws Exception {
|
||||
|
|
Loading…
Add table
Reference in a new issue