mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-15 11:45:51 +00:00
Script: add support for crafting multisig outputs and hide program behind Script.getProgram()
This commit is contained in:
parent
f88bdc5fe3
commit
e18b9d363e
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.core;
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.spongycastle.crypto.digests.RIPEMD160Digest;
|
import org.spongycastle.crypto.digests.RIPEMD160Digest;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@ -28,6 +30,9 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static com.google.bitcoin.core.Utils.bytesToHexString;
|
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
|
* A chunk in a script
|
||||||
@ -60,6 +65,8 @@ class ScriptChunk {
|
|||||||
* static methods for building scripts.</p>
|
* static methods for building scripts.</p>
|
||||||
*/
|
*/
|
||||||
public class Script {
|
public class Script {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(Script.class);
|
||||||
|
|
||||||
// Some constants used for decoding the scripts, copied from the reference client
|
// Some constants used for decoding the scripts, copied from the reference client
|
||||||
// push value
|
// push value
|
||||||
public static final int OP_0 = 0x00;
|
public static final int OP_0 = 0x00;
|
||||||
@ -195,7 +202,7 @@ public class Script {
|
|||||||
|
|
||||||
public static final int OP_INVALIDOPCODE = 0xff;
|
public static final int OP_INVALIDOPCODE = 0xff;
|
||||||
|
|
||||||
byte[] program;
|
private byte[] program;
|
||||||
private int cursor;
|
private int cursor;
|
||||||
|
|
||||||
// The program is a set of byte[]s where each element is either [opcode] or [data, data, data ...]
|
// The program is a set of byte[]s where each element is either [opcode] or [data, data, data ...]
|
||||||
@ -232,6 +239,11 @@ public class Script {
|
|||||||
return buf.toString();
|
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)")
|
* 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());
|
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) {
|
public static byte[] createInputScript(byte[] signature, byte[] pubkey) {
|
||||||
try {
|
try {
|
||||||
// TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes.
|
// 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_CHECKMULTISIG:
|
||||||
case OP_CHECKMULTISIGVERIFY:
|
case OP_CHECKMULTISIGVERIFY:
|
||||||
if (accurate && lastOpCode >= OP_1 && lastOpCode <= OP_16)
|
if (accurate && lastOpCode >= OP_1 && lastOpCode <= OP_16)
|
||||||
sigOps += getOpNValue(lastOpCode);
|
sigOps += decodeFromOpN(lastOpCode);
|
||||||
else
|
else
|
||||||
sigOps += 20;
|
sigOps += 20;
|
||||||
break;
|
break;
|
||||||
@ -731,15 +765,26 @@ public class Script {
|
|||||||
return sigOps;
|
return sigOps;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static int decodeFromOpN(byte opcode) {
|
||||||
* Convince method to get the int value of OP_N
|
return decodeFromOpN(0xFF & opcode);
|
||||||
*/
|
}
|
||||||
private static int getOpNValue(int opcode) throws ScriptException {
|
private static int decodeFromOpN(int opcode) {
|
||||||
|
checkArgument(opcode >= 0 && opcode <= OP_16, "decodeFromOpN called on non OP_N opcode");
|
||||||
if (opcode == OP_0)
|
if (opcode == OP_0)
|
||||||
return 0;
|
return 0;
|
||||||
if (opcode < OP_1 || opcode > OP_16) // This should absolutely never happen
|
else
|
||||||
throw new ScriptException("getOpNValue called on non OP_N opcode");
|
return opcode + 1 - OP_1;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -794,6 +839,32 @@ public class Script {
|
|||||||
(program[22] & 0xff) == OP_EQUAL;
|
(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) {
|
private static boolean equalsRange(byte[] a, int start, byte[] b) {
|
||||||
if (start + b.length > a.length)
|
if (start + b.length > a.length)
|
||||||
return false;
|
return false;
|
||||||
@ -958,7 +1029,7 @@ public class Script {
|
|||||||
case OP_14:
|
case OP_14:
|
||||||
case OP_15:
|
case OP_15:
|
||||||
case OP_16:
|
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;
|
break;
|
||||||
case OP_NOP:
|
case OP_NOP:
|
||||||
break;
|
break;
|
||||||
|
@ -689,6 +689,14 @@ public class Transaction extends ChildMessage implements Serializable {
|
|||||||
addOutput(new TransactionOutput(params, this, value, pubkey));
|
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
|
* 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,
|
* signature is over the transaction itself, to prove the redeemer actually created that transaction,
|
||||||
|
@ -1289,7 +1289,7 @@ public class FullBlockTestGenerator {
|
|||||||
input.setSequenceNumber(sequence);
|
input.setSequenceNumber(sequence);
|
||||||
t.addInput(input);
|
t.addInput(input);
|
||||||
|
|
||||||
byte[] connectedPubKeyScript = prevOut.scriptPubKey.program;
|
byte[] connectedPubKeyScript = prevOut.scriptPubKey.getProgram();
|
||||||
Sha256Hash hash = t.hashTransactionForSignature(0, connectedPubKeyScript, SigHash.ALL, false);
|
Sha256Hash hash = t.hashTransactionForSignature(0, connectedPubKeyScript, SigHash.ALL, false);
|
||||||
|
|
||||||
// Sign input
|
// Sign input
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.core;
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.spongycastle.util.encoders.Hex;
|
import org.spongycastle.util.encoders.Hex;
|
||||||
|
|
||||||
@ -59,6 +60,27 @@ public class ScriptTest {
|
|||||||
assertEquals("mkFQohBpy2HDXrCwyMrYL5RtfrmeiuuPY2", toAddr.toString());
|
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
|
@Test
|
||||||
public void testIp() throws Exception {
|
public void testIp() throws Exception {
|
||||||
byte[] bytes = Hex.decode("41043e96222332ea7848323c08116dddafbfa917b8e37f0bdf63841628267148588a09a43540942d58d49717ad3fabfe14978cf4f0a8b84d2435dad16e9aa4d7f935ac");
|
byte[] bytes = Hex.decode("41043e96222332ea7848323c08116dddafbfa917b8e37f0bdf63841628267148588a09a43540942d58d49717ad3fabfe14978cf4f0a8b84d2435dad16e9aa4d7f935ac");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user