mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-31 23:32:16 +00:00
Script: Introduce a builder class that makes it easier to assemble scripts without writing raw byte streams.
This commit is contained in:
parent
752e7006e5
commit
d113cbfc66
@ -17,6 +17,7 @@
|
||||
package com.google.bitcoin.core;
|
||||
|
||||
import com.google.bitcoin.script.Script;
|
||||
import com.google.bitcoin.script.ScriptBuilder;
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -927,7 +928,8 @@ public class Block extends Message {
|
||||
// Here we will do things a bit differently so a new address isn't needed every time. We'll put a simple
|
||||
// counter in the scriptSig so every transaction has a different hash.
|
||||
coinbase.addInput(new TransactionInput(params, coinbase, new byte[]{(byte) txCounter++, (byte) 1}));
|
||||
coinbase.addOutput(new TransactionOutput(params, coinbase, value, Script.createOutputScript(pubKeyTo)));
|
||||
coinbase.addOutput(new TransactionOutput(params, coinbase, value,
|
||||
ScriptBuilder.createOutputScript(new ECKey(null, pubKeyTo)).getProgram()));
|
||||
transactions.add(coinbase);
|
||||
coinbase.setParent(this);
|
||||
coinbase.length = coinbase.bitcoinSerialize().length;
|
||||
|
@ -18,6 +18,7 @@ package com.google.bitcoin.core;
|
||||
|
||||
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
||||
import com.google.bitcoin.script.Script;
|
||||
import com.google.bitcoin.script.ScriptBuilder;
|
||||
import com.google.bitcoin.script.ScriptOpCodes;
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.slf4j.Logger;
|
||||
@ -714,17 +715,16 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Once a transaction has some inputs and outputs added, the signatures in the inputs can be calculated. The
|
||||
* <p>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,
|
||||
* so we have to do this step last.<p>
|
||||
* <p/>
|
||||
* This method is similar to SignatureHash in script.cpp
|
||||
* so we have to do this step last.</p>
|
||||
*
|
||||
* @param hashType This should always be set to SigHash.ALL currently. Other types are unused.
|
||||
* @param wallet A wallet is required to fetch the keys needed for signing.
|
||||
* @param wallet A wallet is required to fetch the keys needed for signing.
|
||||
* @param aesKey The AES key to use to decrypt the key before signing. Null if no decryption is required.
|
||||
*/
|
||||
public synchronized void signInputs(SigHash hashType, Wallet wallet, KeyParameter aesKey) throws ScriptException {
|
||||
// TODO: This should be a method of the TransactionInput that (possibly?) operates with a copy of this object.
|
||||
Preconditions.checkState(inputs.size() > 0);
|
||||
Preconditions.checkState(outputs.size() > 0);
|
||||
|
||||
@ -738,7 +738,8 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
// Note that each input may be claiming an output sent to a different key. So we have to look at the outputs
|
||||
// to figure out which key to sign with.
|
||||
|
||||
byte[][] signatures = new byte[inputs.size()][];
|
||||
int[] sigHashFlags = new int[inputs.size()];
|
||||
ECKey.ECDSASignature[] signatures = new ECKey.ECDSASignature[inputs.size()];
|
||||
ECKey[] signingKeys = new ECKey[inputs.size()];
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
TransactionInput input = inputs.get(i);
|
||||
@ -756,18 +757,10 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
byte[] connectedPubKeyScript = input.getOutpoint().getConnectedPubKeyScript();
|
||||
Sha256Hash hash = hashTransactionForSignature(i, connectedPubKeyScript, hashType, anyoneCanPay);
|
||||
|
||||
// Now sign for the output so we can redeem it. We use the keypair to sign the hash,
|
||||
// and then put the resulting signature in the script along with the public key (below).
|
||||
try {
|
||||
// Usually 71-73 bytes.
|
||||
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73);
|
||||
bos.write(key.sign(hash, aesKey).encodeToDER());
|
||||
bos.write((hashType.ordinal() + 1) | (anyoneCanPay ? 0x80 : 0));
|
||||
signatures[i] = bos.toByteArray();
|
||||
bos.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
}
|
||||
// Now calculate the signatures we need to prove we own this transaction and are authorized to claim the
|
||||
// associated money.
|
||||
signatures[i] = key.sign(hash, aesKey);
|
||||
sigHashFlags[i] = (hashType.ordinal() + 1) | (anyoneCanPay ? 0x80 : 0);
|
||||
}
|
||||
|
||||
// Now we have calculated each signature, go through and create the scripts. Reminder: the script consists:
|
||||
@ -777,12 +770,11 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
// 2) For pay-to-key outputs: just a signature.
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
TransactionInput input = inputs.get(i);
|
||||
ECKey key = signingKeys[i];
|
||||
Script scriptPubKey = input.getOutpoint().getConnectedOutput().getScriptPubKey();
|
||||
if (scriptPubKey.isSentToAddress()) {
|
||||
input.setScriptBytes(Script.createInputScript(signatures[i], key.getPubKey()));
|
||||
input.setScriptSig(ScriptBuilder.createInputScript(signatures[i], signingKeys[i], sigHashFlags[i]));
|
||||
} else if (scriptPubKey.isSentToRawPubKey()) {
|
||||
input.setScriptBytes(Script.createInputScript(signatures[i]));
|
||||
input.setScriptSig(ScriptBuilder.createInputScript(signatures[i], sigHashFlags[i]));
|
||||
} else {
|
||||
// Should be unreachable - if we don't recognize the type of script we're trying to sign for, we should
|
||||
// have failed above when fetching the key to sign with.
|
||||
|
@ -154,7 +154,8 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input script.
|
||||
* Returns the script that is fed to the referenced output (scriptPubKey) script in order to satisfy it: usually
|
||||
* contains signatures and maybe keys, but can contain arbitrary data if the output script accepts it.
|
||||
*/
|
||||
public Script getScriptSig() throws ScriptException {
|
||||
// Transactions that generate new coins don't actually have a script. Instead this
|
||||
@ -166,6 +167,13 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
return scriptSig;
|
||||
}
|
||||
|
||||
/** Set the given program as the scriptSig that is supposed to satisfy the connected output script. */
|
||||
public void setScriptSig(Script scriptSig) {
|
||||
this.scriptSig = checkNotNull(scriptSig);
|
||||
// TODO: This should all be cleaned up so we have a consistent internal representation.
|
||||
setScriptBytes(scriptSig.getProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that returns the from address of this input by parsing the scriptSig. The concept of a
|
||||
* "from address" is not well defined in Bitcoin and you should not assume that senders of a transaction can
|
||||
|
@ -17,6 +17,7 @@
|
||||
package com.google.bitcoin.core;
|
||||
|
||||
import com.google.bitcoin.script.Script;
|
||||
import com.google.bitcoin.script.ScriptBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -91,7 +92,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
|
||||
* {@link Transaction#addOutput(java.math.BigInteger, Address)} instead of creating a TransactionOutput directly.
|
||||
*/
|
||||
public TransactionOutput(NetworkParameters params, Transaction parent, BigInteger value, Address to) {
|
||||
this(params, parent, value, Script.createOutputScript(to));
|
||||
this(params, parent, value, ScriptBuilder.createOutputScript(to).getProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,7 +101,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
|
||||
* {@link Transaction#addOutput(java.math.BigInteger, ECKey)} instead of creating an output directly.
|
||||
*/
|
||||
public TransactionOutput(NetworkParameters params, Transaction parent, BigInteger value, ECKey to) {
|
||||
this(params, parent, value, Script.createOutputScript(to));
|
||||
this(params, parent, value, ScriptBuilder.createOutputScript(to).getProgram());
|
||||
}
|
||||
|
||||
public TransactionOutput(NetworkParameters params, Transaction parent, BigInteger value, byte[] scriptBytes) {
|
||||
|
@ -60,10 +60,15 @@ public class Script {
|
||||
protected byte[] program;
|
||||
|
||||
/** Creates an empty script that serializes to nothing. */
|
||||
public Script() {
|
||||
private Script() {
|
||||
chunks = Lists.newArrayList();
|
||||
}
|
||||
|
||||
// Used from ScriptBuilder.
|
||||
Script(List<ScriptChunk> chunks) {
|
||||
this.chunks = Collections.unmodifiableList(new ArrayList<ScriptChunk>(chunks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Script that copies and wraps the programBytes array. The array is parsed and checked for syntactic
|
||||
* validity.
|
||||
@ -267,44 +272,6 @@ public class Script {
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] createOutputScript(Address to) {
|
||||
try {
|
||||
// TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes.
|
||||
ByteArrayOutputStream bits = new UnsafeByteArrayOutputStream(24);
|
||||
bits.write(OP_DUP);
|
||||
bits.write(OP_HASH160);
|
||||
writeBytes(bits, to.getHash160());
|
||||
bits.write(OP_EQUALVERIFY);
|
||||
bits.write(OP_CHECKSIG);
|
||||
return bits.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a script that sends coins directly to the given public key (eg in a coinbase transaction).
|
||||
*/
|
||||
public static byte[] createOutputScript(byte[] pubkey) {
|
||||
try {
|
||||
// TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes.
|
||||
ByteArrayOutputStream bits = new UnsafeByteArrayOutputStream(pubkey.length + 1);
|
||||
writeBytes(bits, pubkey);
|
||||
bits.write(OP_CHECKSIG);
|
||||
return bits.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a script that sends coins directly to the given public key. Same as
|
||||
* {@link Script#createOutputScript(byte[])} but more type safe.
|
||||
*/
|
||||
public static byte[] createOutputScript(ECKey pubkey) {
|
||||
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);
|
||||
@ -379,10 +346,10 @@ public class Script {
|
||||
return sigOps;
|
||||
}
|
||||
|
||||
private static int decodeFromOpN(byte opcode) {
|
||||
static int decodeFromOpN(byte opcode) {
|
||||
return decodeFromOpN(0xFF & opcode);
|
||||
}
|
||||
private static int decodeFromOpN(int opcode) {
|
||||
static int decodeFromOpN(int opcode) {
|
||||
checkArgument(opcode >= 0 && opcode <= OP_16, "decodeFromOpN called on non OP_N opcode");
|
||||
if (opcode == OP_0)
|
||||
return 0;
|
||||
@ -390,10 +357,10 @@ public class Script {
|
||||
return opcode + 1 - OP_1;
|
||||
}
|
||||
|
||||
private static int encodeToOpN(byte value) {
|
||||
static int encodeToOpN(byte value) {
|
||||
return encodeToOpN(0xFF & value);
|
||||
}
|
||||
private static int encodeToOpN(int value) {
|
||||
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;
|
||||
@ -1086,6 +1053,7 @@ public class Script {
|
||||
} catch (Exception e1) {
|
||||
// There is (at least) one exception that could be hit here (EOFException, if the sig is too short)
|
||||
// Because I can't verify there aren't more, we use a very generic Exception catch
|
||||
log.warn(e1.toString());
|
||||
sigValid = false;
|
||||
}
|
||||
|
||||
|
113
core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java
Normal file
113
core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* 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 com.google.bitcoin.script;
|
||||
|
||||
import com.google.bitcoin.core.Address;
|
||||
import com.google.bitcoin.core.ECKey;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.bitcoin.script.ScriptOpCodes.*;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
/**
|
||||
* <p>Tools for the construction of commonly used script types. You don't normally need this as it's hidden behind
|
||||
* convenience methods on {@link com.google.bitcoin.core.Transaction}, but they are useful when working with the
|
||||
* protocol at a lower level.</p>
|
||||
*/
|
||||
public class ScriptBuilder {
|
||||
private List<ScriptChunk> chunks;
|
||||
|
||||
public ScriptBuilder() {
|
||||
chunks = Lists.newLinkedList();
|
||||
}
|
||||
|
||||
public ScriptBuilder op(int opcode) {
|
||||
chunks.add(new ScriptChunk(true, new byte[]{(byte)opcode}));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScriptBuilder data(byte[] data) {
|
||||
byte[] copy = Arrays.copyOf(data, data.length);
|
||||
chunks.add(new ScriptChunk(false, copy));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScriptBuilder smallNum(byte num) {
|
||||
checkArgument(num >= 0, "Cannot encode negative numbers with smallNum");
|
||||
checkArgument(num <= 16, "Cannot encode numbers larger than 16 with smallNum");
|
||||
chunks.add(new ScriptChunk(true, new byte[]{(byte)Script.encodeToOpN(num)}));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Script build() {
|
||||
return new Script(chunks);
|
||||
}
|
||||
|
||||
/** Creates a scriptPubKey that encodes payment to the given address. */
|
||||
public static Script createOutputScript(Address to) {
|
||||
// OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
|
||||
return new ScriptBuilder()
|
||||
.op(OP_DUP)
|
||||
.op(OP_HASH160)
|
||||
.data(to.getHash160())
|
||||
.op(OP_EQUALVERIFY)
|
||||
.op(OP_CHECKSIG)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Creates a scriptPubKey that encodes payment to the given raw public key. */
|
||||
public static Script createOutputScript(ECKey key) {
|
||||
return new ScriptBuilder().data(key.getPubKey()).op(OP_CHECKSIG).build();
|
||||
}
|
||||
|
||||
private static byte[] appendByte(byte[] buf, byte flags) {
|
||||
byte[] result = Arrays.copyOf(buf, buf.length + 1);
|
||||
result[result.length - 1] = flags;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Creates a scriptSig that can redeem a pay-to-address output. */
|
||||
public static Script createInputScript(ECKey.ECDSASignature signature, ECKey pubKey, int sigHashFlags) {
|
||||
byte[] pubkeyBytes = pubKey.getPubKey();
|
||||
byte[] sigBytes = appendByte(signature.encodeToDER(), (byte) sigHashFlags);
|
||||
return new ScriptBuilder().data(sigBytes).data(pubkeyBytes).build();
|
||||
}
|
||||
|
||||
/** Creates a scriptSig that can redeem a pay-to-pubkey output. */
|
||||
public static Script createInputScript(ECKey.ECDSASignature signature, int sigHashFlags) {
|
||||
byte[] sigBytes = appendByte(signature.encodeToDER(), (byte) sigHashFlags);
|
||||
return new ScriptBuilder().data(sigBytes).build();
|
||||
}
|
||||
|
||||
/** Creates a program that requires at least N of the given keys to sign, using OP_CHECKMULTISIG. */
|
||||
public static Script 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.
|
||||
ScriptBuilder builder = new ScriptBuilder();
|
||||
builder.smallNum((byte)threshold);
|
||||
for (ECKey key : pubkeys) {
|
||||
builder.data(key.getPubKey());
|
||||
}
|
||||
builder.smallNum((byte)pubkeys.size());
|
||||
builder.op(OP_CHECKMULTISIG);
|
||||
return builder.build();
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package com.google.bitcoin.core;
|
||||
|
||||
import com.google.bitcoin.core.Transaction.SigHash;
|
||||
import com.google.bitcoin.script.Script;
|
||||
import com.google.bitcoin.script.ScriptBuilder;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@ -811,6 +812,7 @@ public class FullBlockTestGenerator {
|
||||
|
||||
// A valid block created exactly like b44 to make sure the creation itself works
|
||||
Block b44 = new Block(params);
|
||||
byte[] outScriptBytes = ScriptBuilder.createOutputScript(new ECKey(null, coinbaseOutKeyPubKey)).getProgram();
|
||||
{
|
||||
b44.setDifficultyTarget(b43.getDifficultyTarget());
|
||||
b44.addCoinbaseTransaction(coinbaseOutKeyPubKey, BigInteger.ZERO);
|
||||
@ -818,7 +820,7 @@ public class FullBlockTestGenerator {
|
||||
Transaction t = new Transaction(params);
|
||||
// Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(0), new byte[] {OP_PUSHDATA1 - 1 }));
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), Script.createOutputScript(coinbaseOutKeyPubKey)));
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), outScriptBytes));
|
||||
// Spendable output
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.ZERO, new byte[] {OP_1}));
|
||||
addOnlyInputToTransaction(t, out14);
|
||||
@ -841,7 +843,7 @@ public class FullBlockTestGenerator {
|
||||
Transaction t = new Transaction(params);
|
||||
// Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(0), new byte[] {OP_PUSHDATA1 - 1 }));
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), Script.createOutputScript(coinbaseOutKeyPubKey)));
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), outScriptBytes));
|
||||
// Spendable output
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.ZERO, new byte[] {OP_1}));
|
||||
addOnlyInputToTransaction(t, out15);
|
||||
@ -917,7 +919,7 @@ public class FullBlockTestGenerator {
|
||||
{
|
||||
Transaction coinbase = new Transaction(params);
|
||||
coinbase.addInput(new TransactionInput(params, coinbase, new byte[]{(byte) 0xff, 110, 1}));
|
||||
coinbase.addOutput(new TransactionOutput(params, coinbase, BigInteger.ONE, Script.createOutputScript(coinbaseOutKeyPubKey)));
|
||||
coinbase.addOutput(new TransactionOutput(params, coinbase, BigInteger.ONE, outScriptBytes));
|
||||
b51.addTransaction(coinbase, false);
|
||||
}
|
||||
b51.solve();
|
||||
@ -1273,7 +1275,8 @@ public class FullBlockTestGenerator {
|
||||
Transaction t = new Transaction(params);
|
||||
// Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(0), new byte[] {OP_PUSHDATA1 - 1 }));
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), Script.createOutputScript(coinbaseOutKeyPubKey)));
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1),
|
||||
ScriptBuilder.createOutputScript(new ECKey(null, coinbaseOutKeyPubKey)).getProgram()));
|
||||
// Spendable output
|
||||
t.addOutput(new TransactionOutput(params, t, BigInteger.ZERO, new byte[] {OP_1}));
|
||||
addOnlyInputToTransaction(t, prevOut);
|
||||
|
@ -66,9 +66,9 @@ public class ScriptTest {
|
||||
@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());
|
||||
assertTrue(ScriptBuilder.createMultiSigOutputScript(2, keys).isSentToMultiSig());
|
||||
assertTrue(ScriptBuilder.createMultiSigOutputScript(3, keys).isSentToMultiSig());
|
||||
assertFalse(ScriptBuilder.createOutputScript(new ECKey()).isSentToMultiSig());
|
||||
try {
|
||||
// Fail if we ask for more signatures than keys.
|
||||
Script.createMultiSigOutputScript(4, keys);
|
||||
@ -82,6 +82,7 @@ public class ScriptTest {
|
||||
} catch (Throwable e) {
|
||||
// Expected.
|
||||
}
|
||||
// Actual execution is tested by the data driven tests.
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
Reference in New Issue
Block a user