3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-14 19:25:51 +00:00

Script: add support for crafting multisig outputs and hide program behind Script.getProgram()

This commit is contained in:
Mike Hearn 2013-04-19 13:13:48 +02:00
parent f88bdc5fe3
commit e18b9d363e
5 changed files with 114 additions and 13 deletions

View File

@ -17,6 +17,8 @@
package com.google.bitcoin.core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.digests.RIPEMD160Digest;
import java.io.ByteArrayOutputStream;
@ -28,6 +30,9 @@ import java.security.NoSuchAlgorithmException;
import java.util.*;
import static com.google.bitcoin.core.Utils.bytesToHexString;
import static com.google.common.base.Preconditions.checkArgument;
// TODO: Make this class a superclass with derived classes giving accessor methods for the various common templates.
/**
* A chunk in a script
@ -60,6 +65,8 @@ class ScriptChunk {
* static methods for building scripts.</p>
*/
public class Script {
private static final Logger log = LoggerFactory.getLogger(Script.class);
// Some constants used for decoding the scripts, copied from the reference client
// push value
public static final int OP_0 = 0x00;
@ -195,7 +202,7 @@ public class Script {
public static final int OP_INVALIDOPCODE = 0xff;
byte[] program;
private byte[] program;
private int cursor;
// The program is a set of byte[]s where each element is either [opcode] or [data, data, data ...]
@ -231,6 +238,11 @@ public class Script {
}
return buf.toString();
}
/** Returns the serialized program as a newly created byte array. */
public byte[] getProgram() {
return Arrays.copyOf(program, program.length);
}
/**
* Converts the given OpCode into a string (eg "0", "PUSHDATA", or "NON_OP(10)")
@ -679,6 +691,28 @@ public class Script {
return createOutputScript(pubkey.getPubKey());
}
/** Creates a program that requires at least N of the given keys to sign, using OP_CHECKMULTISIG. */
public static byte[] createMultiSigOutputScript(int threshold, List<ECKey> pubkeys) {
checkArgument(threshold > 0);
checkArgument(threshold <= pubkeys.size());
checkArgument(pubkeys.size() <= 16); // That's the max we can represent with a single opcode.
if (pubkeys.size() > 3) {
log.warn("Creating a multi-signature output that is non-standard: {} pubkeys, should be <= 3", pubkeys.size());
}
try {
ByteArrayOutputStream bits = new ByteArrayOutputStream();
bits.write(encodeToOpN(threshold));
for (ECKey key : pubkeys) {
writeBytes(bits, key.getPubKey());
}
bits.write(encodeToOpN(pubkeys.size()));
bits.write(OP_CHECKMULTISIG);
return bits.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
public static byte[] createInputScript(byte[] signature, byte[] pubkey) {
try {
// TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes.
@ -718,7 +752,7 @@ public class Script {
case OP_CHECKMULTISIG:
case OP_CHECKMULTISIGVERIFY:
if (accurate && lastOpCode >= OP_1 && lastOpCode <= OP_16)
sigOps += getOpNValue(lastOpCode);
sigOps += decodeFromOpN(lastOpCode);
else
sigOps += 20;
break;
@ -731,15 +765,26 @@ public class Script {
return sigOps;
}
/**
* Convince method to get the int value of OP_N
*/
private static int getOpNValue(int opcode) throws ScriptException {
private static int decodeFromOpN(byte opcode) {
return decodeFromOpN(0xFF & opcode);
}
private static int decodeFromOpN(int opcode) {
checkArgument(opcode >= 0 && opcode <= OP_16, "decodeFromOpN called on non OP_N opcode");
if (opcode == OP_0)
return 0;
if (opcode < OP_1 || opcode > OP_16) // This should absolutely never happen
throw new ScriptException("getOpNValue called on non OP_N opcode");
return opcode + 1 - OP_1;
else
return opcode + 1 - OP_1;
}
private static int encodeToOpN(byte value) {
return encodeToOpN(0xFF & value);
}
private static int encodeToOpN(int value) {
checkArgument(value >= 0 && value <= 16, "encodeToOpN called for a value we cannot encode in an opcode.");
if (value == 0)
return OP_0;
else
return value - 1 + OP_1;
}
/**
@ -793,7 +838,33 @@ public class Script {
(program[1] & 0xff) == 0x14 &&
(program[22] & 0xff) == OP_EQUAL;
}
/**
* 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.data[0]);
if (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.
decodeFromOpN(chunks.get(0).data[0]);
} catch (IllegalStateException e) {
return false; // Not an OP_N opcode.
}
return true;
}
private static boolean equalsRange(byte[] a, int start, byte[] b) {
if (start + b.length > a.length)
return false;
@ -958,7 +1029,7 @@ public class Script {
case OP_14:
case OP_15:
case OP_16:
stack.add(Utils.reverseBytes(Utils.encodeMPI(BigInteger.valueOf(getOpNValue(opcode)), false)));
stack.add(Utils.reverseBytes(Utils.encodeMPI(BigInteger.valueOf(decodeFromOpN(opcode)), false)));
break;
case OP_NOP:
break;

View File

@ -689,6 +689,14 @@ public class Transaction extends ChildMessage implements Serializable {
addOutput(new TransactionOutput(params, this, value, pubkey));
}
/**
* Creates an output that pays to the given script. The address and key forms are specialisations of this method,
* you won't normally need to use it unless you're doing unusual things.
*/
public void addOutput(BigInteger value, Script script) {
addOutput(new TransactionOutput(params, this, value, script.getProgram()));
}
/**
* Once a transaction has some inputs and outputs added, the signatures in the inputs can be calculated. The
* signature is over the transaction itself, to prove the redeemer actually created that transaction,

View File

@ -1289,7 +1289,7 @@ public class FullBlockTestGenerator {
input.setSequenceNumber(sequence);
t.addInput(input);
byte[] connectedPubKeyScript = prevOut.scriptPubKey.program;
byte[] connectedPubKeyScript = prevOut.scriptPubKey.getProgram();
Sha256Hash hash = t.hashTransactionForSignature(0, connectedPubKeyScript, SigHash.ALL, false);
// Sign input

View File

@ -16,6 +16,7 @@
package com.google.bitcoin.core;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.spongycastle.util.encoders.Hex;
@ -59,6 +60,27 @@ public class ScriptTest {
assertEquals("mkFQohBpy2HDXrCwyMrYL5RtfrmeiuuPY2", toAddr.toString());
}
@Test
public void testMultiSig() throws Exception {
List<ECKey> keys = Lists.newArrayList(new ECKey(), new ECKey(), new ECKey());
assertTrue(new Script(Script.createMultiSigOutputScript(2, keys)).isSentToMultiSig());
assertTrue(new Script(Script.createMultiSigOutputScript(3, keys)).isSentToMultiSig());
assertFalse(new Script(Script.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.
}
}
@Test
public void testIp() throws Exception {
byte[] bytes = Hex.decode("41043e96222332ea7848323c08116dddafbfa917b8e37f0bdf63841628267148588a09a43540942d58d49717ad3fabfe14978cf4f0a8b84d2435dad16e9aa4d7f935ac");

View File

@ -710,7 +710,7 @@ public class WalletTest extends TestWithWallet {
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
super.onTransactionConfidenceChanged(wallet, tx);
if (tx.getConfidence().getConfidenceType() ==
if (tx.getConfidence().getConfidenceType() ==
TransactionConfidence.ConfidenceType.DEAD) {
called[0] = tx;
called[1] = tx.getConfidence().getOverridingTransaction();