diff --git a/pom.xml b/pom.xml
index 86867e10a..483896b0d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
co.rsk.bitcoinj
- 0.14.4-rsk-18_2-SNAPSHOT
+ 0.14.4-rsk-18
bitcoinj-thin
bitcoinj-thin
diff --git a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java
index 7f769d9cb..1a632d2e0 100644
--- a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java
+++ b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java
@@ -1,12 +1,14 @@
package co.rsk.bitcoinj.core;
import co.rsk.bitcoinj.crypto.TransactionSignature;
+import co.rsk.bitcoinj.script.RedeemScriptParser;
+import co.rsk.bitcoinj.script.RedeemScriptParserFactory;
+import co.rsk.bitcoinj.script.Script;
+import com.google.common.base.Preconditions;
import javax.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
+import static com.google.common.base.Preconditions.checkState;
public class TransactionWitness {
static TransactionWitness empty = new TransactionWitness(0);
@@ -66,6 +68,105 @@ public byte[] getScriptBytes() {
return pushes.get(pushes.size() - 1);
}
+ /**
+ replicated logic from {@link Script#getSigInsertionIndex}
+ * */
+ public int getSigInsertionIndex(Sha256Hash sigHash, BtcECKey signingKey) {
+ int witnessSize = getPushCount();
+ int redeemScriptIndex = witnessSize - 1;
+ byte[] redeemScriptData = getPush(redeemScriptIndex);
+ Script redeemScript = new Script(redeemScriptData);
+ RedeemScriptParser redeemScriptParser = RedeemScriptParserFactory.get(redeemScript.getChunks());
+
+ int sigInsertionIndex = 0;
+ int keyIndexInRedeem = redeemScriptParser.findKeyInRedeem(signingKey);
+
+ byte[] emptyByte = new byte[]{};
+ // the pushes that should have the signatures
+ // are between first one (empty byte for checkmultisig bug)
+ // and second to last one (op_notif + redeem script)
+ for (int i = 1; i < getPushCount() - 1; i ++) {
+ byte[] push = getPush(i);
+ Preconditions.checkNotNull(push);
+ if (!Arrays.equals(push, emptyByte)) {
+ if (keyIndexInRedeem < redeemScriptParser.findSigInRedeem(push, sigHash)) {
+ return sigInsertionIndex;
+ }
+
+ sigInsertionIndex++;
+ }
+ }
+
+ return sigInsertionIndex;
+ }
+
+ /**
+ replicated logic from {@link Script#getScriptSigWithSignature}
+ * */
+ public TransactionWitness updateWitnessWithSignature(Script outputScript, byte[] signature, int targetIndex) {
+ int sigsPrefixCount = outputScript.getSigsPrefixCount();
+ int sigsSuffixCount = outputScript.getSigsSuffixCount();
+ return updateWitnessWithSignature(signature, targetIndex, sigsPrefixCount, sigsSuffixCount);
+ }
+
+ /**
+ replicated logic from {@link co.rsk.bitcoinj.script.ScriptBuilder#updateScriptWithSignature}
+ * */
+ private TransactionWitness updateWitnessWithSignature(byte[] signature, int targetIndex, int sigsPrefixCount, int sigsSuffixCount) {
+ int totalPushes = getPushCount();
+
+ byte[] emptyByte = new byte[]{};
+ // since we fill the signatures in order, checking
+ // the second to last push is enough to know
+ // if there's space for new signatures
+ byte[] secondToLastPush = getPush(totalPushes - sigsSuffixCount - 1);
+ boolean hasMissingSigs = Arrays.equals(secondToLastPush, emptyByte);
+ Preconditions.checkArgument(hasMissingSigs, "Witness script is already filled with signatures");
+
+ List updatedPushes = new ArrayList<>();
+ // the signatures appear after the prefix
+ for (int i = 0; i < sigsPrefixCount; i++) {
+ byte[] push = getPush(i);
+ updatedPushes.add(push);
+ }
+
+ int index = 0;
+ boolean inserted = false;
+ // copy existing sigs
+ for (int i = sigsPrefixCount; i < totalPushes - sigsSuffixCount; i++) {
+ if (index == targetIndex) {
+ inserted = true;
+ updatedPushes.add(signature);
+ ++index;
+ }
+
+ byte[] push = getPush(i);
+ if (!Arrays.equals(push, emptyByte)) {
+ updatedPushes.add(push);
+ ++index;
+ }
+ }
+
+ // add zeros for missing signatures
+ while (index < totalPushes - sigsPrefixCount - sigsSuffixCount) {
+ if (index == targetIndex) {
+ inserted = true;
+ updatedPushes.add(signature);
+ } else {
+ updatedPushes.add(emptyByte);
+ }
+ index++;
+ }
+
+ // copy the suffix
+ for (int i = totalPushes - sigsSuffixCount; i < totalPushes; i++) {
+ byte[] push = getPush(i);
+ updatedPushes.add(push);
+ }
+
+ checkState(inserted);
+ return TransactionWitness.of(updatedPushes);
+ }
@Override
public boolean equals(Object otherObject) {
diff --git a/src/main/java/co/rsk/bitcoinj/script/RedeemScriptValidator.java b/src/main/java/co/rsk/bitcoinj/script/RedeemScriptValidator.java
index 29d708a06..d3a1afaa4 100644
--- a/src/main/java/co/rsk/bitcoinj/script/RedeemScriptValidator.java
+++ b/src/main/java/co/rsk/bitcoinj/script/RedeemScriptValidator.java
@@ -5,6 +5,10 @@
public class RedeemScriptValidator {
+ private RedeemScriptValidator() {
+ // Prevent instantiation
+ }
+
protected static boolean isRedeemLikeScript(List chunks) {
if (chunks.size() < 4) {
return false;
@@ -12,9 +16,7 @@ protected static boolean isRedeemLikeScript(List chunks) {
ScriptChunk lastChunk = chunks.get(chunks.size() - 1);
// A standard multisig redeem script must end in OP_CHECKMULTISIG[VERIFY]
- boolean isStandard = lastChunk.isOpCode() &&
- (lastChunk.equalsOpCode(ScriptOpCodes.OP_CHECKMULTISIG) ||
- lastChunk.equalsOpCode(ScriptOpCodes.OP_CHECKMULTISIGVERIFY));
+ boolean isStandard = lastChunk.isOpCheckMultiSig();
if (isStandard) {
return true;
}
@@ -23,9 +25,7 @@ protected static boolean isRedeemLikeScript(List chunks) {
ScriptChunk penultimateChunk = chunks.get(chunks.size() - 2);
return lastChunk.isOpCode() &&
lastChunk.equalsOpCode(ScriptOpCodes.OP_ENDIF) &&
- penultimateChunk.isOpCode() &&
- (penultimateChunk.equalsOpCode(ScriptOpCodes.OP_CHECKMULTISIG) ||
- penultimateChunk.equalsOpCode(ScriptOpCodes.OP_CHECKMULTISIGVERIFY));
+ penultimateChunk.isOpCheckMultiSig();
}
protected static boolean hasStandardRedeemScriptStructure(List chunks) {
@@ -34,24 +34,30 @@ protected static boolean hasStandardRedeemScriptStructure(List chun
return false;
}
- // First chunk must be an OP_N
- if (!isOpN(chunks.get(0))) {
+ // last chunk should be OP_CHECKMULTISIG
+ int chunksSize = chunks.size();
+ ScriptChunk lastChunk = chunks.get(chunksSize - 1);
+ if (!lastChunk.isOpCheckMultiSig()) {
return false;
}
- // Second to last chunk must be an OP_N opcode too, and there should be
- // that many data chunks (keys).
- ScriptChunk secondToLastChunk = chunks.get(chunks.size() - 2);
- if (!isOpN(secondToLastChunk)) {
+ // First chunk must be a number for the threshold
+ ScriptChunk firstChunk = chunks.get(0);
+ // Second to last chunk must be a number for the keys
+ int secondToLastChunkIndex = chunksSize - 2;
+ ScriptChunk secondToLastChunk = chunks.get(secondToLastChunkIndex);
+
+ if (!(firstChunk.isPositiveN() && secondToLastChunk.isPositiveN())) {
return false;
}
- int numKeys = Script.decodeFromOpN(secondToLastChunk.opcode);
- if (numKeys < 1 || chunks.size() != numKeys + 3) { // numKeys + M + N + OP_CHECKMULTISIG
+ int numKeys = secondToLastChunk.decodePositiveN();
+ // and there should be numKeys+3 total chunks (keys + OP_M + OP_N + OP_CHECKMULTISIG)
+ if (chunksSize != numKeys + 3) {
return false;
}
- for (int i = 1; i < chunks.size() - 2; i++) {
+ for (int i = 1; i < secondToLastChunkIndex; i++) {
if (chunks.get(i).isOpCode()) { // Should be the public keys, not op_codes
return false;
}
@@ -59,7 +65,7 @@ protected static boolean hasStandardRedeemScriptStructure(List chun
return true;
} catch (IllegalStateException e) {
- return false; // Not an OP_N opcode.
+ return false; // Not a number
}
}
@@ -68,10 +74,11 @@ protected static boolean hasP2shErpRedeemScriptStructure(List chunk
return false;
}
- ScriptChunk firstChunk = chunks.get(0);
+ int opNotifIndex = 0;
+ boolean hasErpPrefix = chunks.get(opNotifIndex).equalsOpCode(ScriptOpCodes.OP_NOTIF);
- boolean hasErpPrefix = firstChunk.opcode == ScriptOpCodes.OP_NOTIF;
- boolean hasEndIfOpcode = chunks.get(chunks.size() - 1).equalsOpCode(ScriptOpCodes.OP_ENDIF);
+ int lastChunkIndex = chunks.size() - 1;
+ boolean hasEndIfOpcode = chunks.get(lastChunkIndex).equalsOpCode(ScriptOpCodes.OP_ENDIF);
if (!hasErpPrefix || !hasEndIfOpcode) {
return false;
@@ -101,8 +108,7 @@ protected static boolean hasP2shErpRedeemScriptStructure(List chunk
return false;
}
- /***
- * Expected structure:
+ /* The redeem script structure should be as follows:
* OP_NOTIF
* OP_M
* PUBKEYS...N
@@ -119,7 +125,6 @@ protected static boolean hasP2shErpRedeemScriptStructure(List chunk
* OP_CHECKMULTISIG
* OP_ENDIF
*/
-
// Validate both default and erp federations redeem scripts.
// Extract the default PowPeg and the emergency multisig redeemscript chunks
List defaultFedRedeemScriptChunks = chunks.subList(1, elseOpcodeIndex);
@@ -218,9 +223,4 @@ protected static List removeOpCheckMultisig(Script redeemScript) {
// Remove the last chunk, which has CHECKMULTISIG op code
return redeemScript.getChunks().subList(0, redeemScript.getChunks().size() - 1);
}
-
- protected static boolean isOpN(ScriptChunk chunk) {
- return chunk.isOpCode() &&
- chunk.opcode >= ScriptOpCodes.OP_1 && chunk.opcode <= ScriptOpCodes.OP_16;
- }
}
diff --git a/src/main/java/co/rsk/bitcoinj/script/Script.java b/src/main/java/co/rsk/bitcoinj/script/Script.java
index 23a8a8731..1cdbac714 100644
--- a/src/main/java/co/rsk/bitcoinj/script/Script.java
+++ b/src/main/java/co/rsk/bitcoinj/script/Script.java
@@ -22,15 +22,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-import co.rsk.bitcoinj.core.Address;
-import co.rsk.bitcoinj.core.BtcECKey;
-import co.rsk.bitcoinj.core.BtcTransaction;
-import co.rsk.bitcoinj.core.NetworkParameters;
-import co.rsk.bitcoinj.core.ProtocolException;
-import co.rsk.bitcoinj.core.ScriptException;
-import co.rsk.bitcoinj.core.Sha256Hash;
-import co.rsk.bitcoinj.core.UnsafeByteArrayOutputStream;
-import co.rsk.bitcoinj.core.Utils;
+import co.rsk.bitcoinj.core.*;
import co.rsk.bitcoinj.crypto.TransactionSignature;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
@@ -40,14 +32,7 @@
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -97,6 +82,9 @@ public enum VerifyFlag {
private static final Logger log = LoggerFactory.getLogger(Script.class);
public static final long MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes
+
+ /** The maximum size in bytes of a standard witnessScript */
+ public static final long MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600;
public static final int SIG_SIZE = 75;
/** Max number of sigops allowed in a standard p2sh redeem script */
public static final int MAX_P2SH_SIGOPS = 15;
@@ -459,19 +447,25 @@ private boolean isErpType(Script redeemScript) {
* Returns a copy of the given scriptSig with the signature inserted in the given position.
*/
public Script getScriptSigWithSignature(Script scriptSig, byte[] sigBytes, int index) {
- int sigsPrefixCount = 0;
- int sigsSuffixCount = 0;
- if (isPayToScriptHash()) {
- sigsPrefixCount = 1; // OP_0 *
- sigsSuffixCount = 1;
- } else if (isSentToMultiSig()) {
- sigsPrefixCount = 1; // OP_0 *
- } else if (isSentToAddress()) {
- sigsSuffixCount = 1; //
- }
+ int sigsPrefixCount = getSigsPrefixCount();
+ int sigsSuffixCount = getSigsSuffixCount();
return ScriptBuilder.updateScriptWithSignature(scriptSig, sigBytes, index, sigsPrefixCount, sigsSuffixCount);
}
+ public int getSigsPrefixCount() {
+ if (isPayToScriptHash() || isSentToMultiSig()) { // OP_0 * || OP_0 *
+ return 1;
+ }
+ return 0;
+ }
+
+ public int getSigsSuffixCount() {
+ if (isPayToScriptHash() || isSentToAddress()) { // * ||
+ return 1;
+ }
+ return 0;
+ }
+
private RedeemScriptParser getRedeemScriptParser() {
if (redeemScriptParser == null){
redeemScriptParser = RedeemScriptParserFactory.get(chunks);
@@ -554,22 +548,24 @@ private static int getSigOpCount(List chunks, boolean accurate) thr
static int decodeFromOpN(int opcode) {
checkArgument((opcode == OP_0 || opcode == OP_1NEGATE) || (opcode >= OP_1 && opcode <= OP_16), "decodeFromOpN called on non OP_N opcode");
- if (opcode == OP_0)
+ if (opcode == OP_0) {
return 0;
- else if (opcode == OP_1NEGATE)
+ } else if (opcode == OP_1NEGATE) {
return -1;
- else
+ } else {
return opcode + 1 - OP_1;
+ }
}
static int encodeToOpN(int value) {
checkArgument(value >= -1 && value <= 16, "encodeToOpN called for " + value + " which we cannot encode in an opcode.");
- if (value == 0)
+ if (value == 0) {
return OP_0;
- else if (value == -1)
+ } else if (value == -1) {
return OP_1NEGATE;
- else
+ } else {
return value - 1 + OP_1;
+ }
}
/**
diff --git a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java
index a2c92dde4..d202e25e4 100644
--- a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java
+++ b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java
@@ -38,7 +38,7 @@
* protocol at a lower level.
*/
public class ScriptBuilder {
- private List chunks;
+ private final List chunks;
/** Creates a fresh ScriptBuilder with an empty program. */
public ScriptBuilder() {
@@ -47,7 +47,7 @@ public ScriptBuilder() {
/** Creates a fresh ScriptBuilder with the given program as the starting point. */
public ScriptBuilder(Script template) {
- chunks = new ArrayList(template.getChunks());
+ chunks = new ArrayList<>(template.getChunks());
}
/** Adds the given chunk to the end of the program */
@@ -63,7 +63,7 @@ public ScriptBuilder addChunk(int index, ScriptChunk chunk) {
/** Adds the given list of chunks to the end of the program */
public ScriptBuilder addChunks(List chunks) {
- chunks.forEach(chunk -> addChunk(chunk));
+ chunks.forEach(this::addChunk);
return this;
}
@@ -80,10 +80,11 @@ public ScriptBuilder op(int index, int opcode) {
/** Adds a copy of the given byte array as a data element (i.e. PUSHDATA) at the end of the program. */
public ScriptBuilder data(byte[] data) {
- if (data.length == 0)
+ if (data.length == 0) {
return smallNum(0);
- else
+ } else {
return data(chunks.size(), data);
+ }
}
/** Adds a copy of the given byte array as a data element (i.e. PUSHDATA) at the given index in the program. */
@@ -95,10 +96,11 @@ public ScriptBuilder data(int index, byte[] data) {
opcode = OP_0;
} else if (data.length == 1) {
byte b = data[0];
- if (b >= 1 && b <= 16)
+ if (b >= 1 && b <= 16) {
opcode = Script.encodeToOpN(b);
- else
+ } else {
opcode = 1;
+ }
} else if (data.length < OP_PUSHDATA1) {
opcode = data.length;
} else if (data.length < 256) {
@@ -267,13 +269,13 @@ public static Script createInputScript(@Nullable TransactionSignature signature)
public static Script createMultiSigOutputScript(int threshold, List pubkeys) {
checkArgument(threshold > 0);
checkArgument(threshold <= pubkeys.size());
- checkArgument(pubkeys.size() <= 16); // That's the max we can represent with a single opcode.
+ checkArgument(pubkeys.size() <= 20); // That's the max OP_CHECKMULTISIG allows.
ScriptBuilder builder = new ScriptBuilder();
- builder.smallNum(threshold);
+ builder.number(threshold);
for (BtcECKey key : pubkeys) {
builder.data(key.getPubKey());
}
- builder.smallNum(pubkeys.size());
+ builder.number(pubkeys.size());
builder.op(OP_CHECKMULTISIG);
return builder.build();
}
@@ -430,7 +432,7 @@ public static Script createP2SHP2WSHOutputScript(Script redeemScript) {
.number(ScriptOpCodes.OP_0)
.data(redeemScriptHash)
.build();
- return ScriptBuilder.createP2SHOutputScript(witnessScript);
+ return createP2SHOutputScript(witnessScript);
}
/**
diff --git a/src/main/java/co/rsk/bitcoinj/script/ScriptChunk.java b/src/main/java/co/rsk/bitcoinj/script/ScriptChunk.java
index 14d4463b5..a8a42d841 100644
--- a/src/main/java/co/rsk/bitcoinj/script/ScriptChunk.java
+++ b/src/main/java/co/rsk/bitcoinj/script/ScriptChunk.java
@@ -17,16 +17,17 @@
package co.rsk.bitcoinj.script;
+import static co.rsk.bitcoinj.script.ScriptOpCodes.*;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.isNull;
+
import co.rsk.bitcoinj.core.Utils;
import com.google.common.base.Objects;
-
-import javax.annotation.Nullable;
import java.io.IOException;
import java.io.OutputStream;
+import java.math.BigInteger;
import java.util.Arrays;
-
-import static com.google.common.base.Preconditions.checkState;
-import static co.rsk.bitcoinj.script.ScriptOpCodes.*;
+import javax.annotation.Nullable;
/**
* A script element that is either a data push (signature, pubkey, etc) or a non-push (logic, numeric, etc) operation.
@@ -40,7 +41,7 @@ public class ScriptChunk {
*/
@Nullable
public final byte[] data;
- private int startLocationInProgram;
+ private final int startLocationInProgram;
public ScriptChunk(int opcode, byte[] data) {
this(opcode, data, -1);
@@ -76,7 +77,7 @@ public int getStartLocationInProgram() {
}
/** If this chunk is an OP_N opcode returns the equivalent integer value. */
- public int decodeOpN() {
+ private int decodeOpN() {
checkState(isOpCode());
return Script.decodeFromOpN(opcode);
}
@@ -86,23 +87,30 @@ public int decodeOpN() {
*/
public boolean isShortestPossiblePushData() {
checkState(isPushData());
- if (data == null)
+ if (data == null) {
return true; // OP_N
- if (data.length == 0)
+ }
+ if (data.length == 0) {
return opcode == OP_0;
+ }
if (data.length == 1) {
byte b = data[0];
- if (b >= 0x01 && b <= 0x10)
+ if (b >= 0x01 && b <= 0x10) {
return opcode == OP_1 + b - 1;
- if ((b & 0xFF) == 0x81)
+ }
+ if ((b & 0xFF) == 0x81) {
return opcode == OP_1NEGATE;
+ }
}
- if (data.length < OP_PUSHDATA1)
+ if (data.length < OP_PUSHDATA1) {
return opcode == data.length;
- if (data.length < 256)
+ }
+ if (data.length < 256) {
return opcode == OP_PUSHDATA1;
- if (data.length < 65536)
+ }
+ if (data.length < 65536) {
return opcode == OP_PUSHDATA2;
+ }
// can never be used, but implemented for completeness
return opcode == OP_PUSHDATA4;
@@ -138,6 +146,57 @@ public void write(OutputStream stream) throws IOException {
}
}
+ public boolean isPositiveN() {
+ try {
+ decodePositiveN();
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ public int decodePositiveN() {
+ if (isOpcodeSmallNumber()) {
+ return decodeOpN();
+ }
+
+ if (isPushData()) {
+ return decodePositiveNConsideringEncoding();
+ }
+
+ throw new IllegalArgumentException("Cannot decode positive number from chunk");
+ }
+
+ private boolean isOpcodeSmallNumber() {
+ return isOpCode()
+ && opcode >= ScriptOpCodes.OP_1
+ && opcode <= ScriptOpCodes.OP_16;
+ }
+
+ public boolean isOpCheckMultiSig() {
+ return isOpCode() &&
+ (opcode == ScriptOpCodes.OP_CHECKMULTISIG || opcode == ScriptOpCodes.OP_CHECKMULTISIGVERIFY);
+ }
+
+ private int decodePositiveNConsideringEncoding() {
+ if (isNull(data)) {
+ throw new IllegalArgumentException("Chunk has null data.");
+ }
+ int dataLength = data.length;
+
+ int signByte = data[dataLength - 1] & 0x80;
+ boolean isPositive = signByte == 0;
+ if (!isPositive) {
+ throw new IllegalArgumentException("Number from chunk is not positive.");
+ }
+
+ if (dataLength > 4) {
+ throw new IllegalArgumentException("Number from chunk has more than 4 bytes.");
+ }
+ BigInteger bigInteger = Utils.decodeMPI(Utils.reverseBytes(data), false);
+ return bigInteger.intValue(); // values up to Integer.MAX_VALUE can be cast as ints
+ }
+
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
@@ -155,11 +214,17 @@ public String toString() {
@Override
public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
ScriptChunk other = (ScriptChunk) o;
- return opcode == other.opcode && startLocationInProgram == other.startLocationInProgram
- && Arrays.equals(data, other.data);
+ return opcode == other.opcode &&
+ startLocationInProgram == other.startLocationInProgram &&
+ Arrays.equals(data, other.data);
}
@Override
diff --git a/src/main/java/co/rsk/bitcoinj/script/StandardRedeemScriptParser.java b/src/main/java/co/rsk/bitcoinj/script/StandardRedeemScriptParser.java
index 599ff4404..ed7feec16 100644
--- a/src/main/java/co/rsk/bitcoinj/script/StandardRedeemScriptParser.java
+++ b/src/main/java/co/rsk/bitcoinj/script/StandardRedeemScriptParser.java
@@ -4,48 +4,46 @@
import co.rsk.bitcoinj.core.BtcECKey;
import co.rsk.bitcoinj.core.Sha256Hash;
-import co.rsk.bitcoinj.core.Utils;
import co.rsk.bitcoinj.crypto.TransactionSignature;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import org.spongycastle.util.encoders.Hex;
public class StandardRedeemScriptParser implements RedeemScriptParser {
// In case of P2SH represents a scriptSig, where the last chunk is the redeem script (either standard or extended)
- // Standard redeem script
protected List redeemScriptChunks;
- StandardRedeemScriptParser(
- List redeemScriptChunks
- ) {
+ StandardRedeemScriptParser(List redeemScriptChunks) {
this.redeemScriptChunks = redeemScriptChunks;
}
@Override
public int getM() {
- checkArgument(redeemScriptChunks.get(0).isOpCode());
- return Script.decodeFromOpN(redeemScriptChunks.get(0).opcode);
+ ScriptChunk firstChunk = redeemScriptChunks.get(0);
+ return firstChunk.decodePositiveN();
}
@Override
public int findKeyInRedeem(BtcECKey key) {
- checkArgument(redeemScriptChunks.get(0).isOpCode()); // P2SH scriptSig
- int numKeys = Script.decodeFromOpN(redeemScriptChunks.get(redeemScriptChunks.size() - 2).opcode);
+ int numKeys = getN();
for (int i = 0; i < numKeys; i++) {
if (Arrays.equals(redeemScriptChunks.get(1 + i).data, key.getPubKey())) {
return i;
}
}
- throw new IllegalStateException("Could not find matching key " + key.toString() + " in script " + this);
+ throw new IllegalStateException(String.format(
+ "Could not find matching key %s in script", key.getPublicKeyAsHex()
+ ));
}
@Override
public List getPubKeys() {
ArrayList result = Lists.newArrayList();
- int numKeys = Script.decodeFromOpN(redeemScriptChunks.get(redeemScriptChunks.size() - 2).opcode);
+ int numKeys = getN();
for (int i = 0; i < numKeys; i++) {
result.add(BtcECKey.fromPublicOnly(redeemScriptChunks.get(1 + i).data));
}
@@ -56,18 +54,16 @@ public List getPubKeys() {
@Override
public int findSigInRedeem(byte[] signatureBytes, Sha256Hash hash) {
checkArgument(redeemScriptChunks.get(0).isOpCode()); // P2SH scriptSig
- int numKeys = Script.decodeFromOpN(redeemScriptChunks.get(redeemScriptChunks.size() - 2).opcode);
- TransactionSignature signature = TransactionSignature
- .decodeFromBitcoin(signatureBytes, true);
+ int numKeys = getN();
+ TransactionSignature signature = TransactionSignature.decodeFromBitcoin(signatureBytes, true);
for (int i = 0; i < numKeys; i++) {
if (BtcECKey.fromPublicOnly(redeemScriptChunks.get(i + 1).data).verify(hash, signature)) {
return i;
}
}
- throw new IllegalStateException(
- "Could not find matching key for signature on " + hash.toString() + " sig "
- + Utils.HEX.encode(signatureBytes)
- );
+ throw new IllegalStateException(String.format(
+ "Could not find matching key for signature %s on %s", Hex.toHexString(signatureBytes), hash
+ ));
}
@Override
@@ -79,4 +75,9 @@ public List extractStandardRedeemScriptChunks() {
public boolean hasErpFormat() {
return false;
}
+
+ private int getN() {
+ ScriptChunk secondToLastChunk = redeemScriptChunks.get(redeemScriptChunks.size() - 2); // OP_N, last chunk is OP_CHECKMULTISIG
+ return secondToLastChunk.decodePositiveN();
+ }
}
diff --git a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java
index 2d054402a..75c13ec8d 100644
--- a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java
+++ b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java
@@ -654,13 +654,27 @@ public void completeTx(SendRequest req) throws InsufficientMoneyException {
if (req.shuffleOutputs)
req.tx.shuffleOutputs();
- // Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
+ // Now sign the legacy inputs, thus proving that we are entitled to redeem the connected outputs.
if (req.signInputs)
- signTransaction(req);
+ signLegacyTransaction(req);
+
+ // Check virtual size.
+ int baseSize = req.tx.unsafeBitcoinSerialize().length;
+ int totalSize = baseSize;
+ // if the tx was signed, there's nothing else to consider
+ if (!req.signInputs) {
+ if (req.isSegwitCompatible) {
+ baseSize += calculateSegwitScriptSigSize(req.tx);
+ totalSize = baseSize;
+ totalSize += estimateBytesForSigning(bestCoinSelection);
+ } else {
+ baseSize += estimateBytesForSigning(bestCoinSelection);
+ totalSize = baseSize;
+ }
+ }
- // Check size.
- final int size = req.tx.unsafeBitcoinSerialize().length;
- if (size > BtcTransaction.MAX_STANDARD_TX_SIZE)
+ int virtualSize = calculateVirtualSize(baseSize, totalSize);
+ if (virtualSize > BtcTransaction.MAX_STANDARD_TX_SIZE)
throw new ExceededMaxTransactionSize();
// Label the transaction as being a user requested payment. This can be used to render GUI wallet
@@ -676,12 +690,12 @@ public void completeTx(SendRequest req) throws InsufficientMoneyException {
}
/**
- * Given a send request containing transaction, attempts to sign it's inputs. This method expects transaction
+ *
Given a send request containing a legacy transaction, attempts to sign it's inputs. This method expects transaction
* to have all necessary inputs connected or they will be ignored.
* Actual signing is done by pluggable {@link #signers} and it's not guaranteed that
* transaction will be complete in the end.
*/
- public void signTransaction(SendRequest req) {
+ public void signLegacyTransaction(SendRequest req) {
try {
BtcTransaction tx = req.tx;
List inputs = tx.getInputs();
@@ -731,7 +745,7 @@ private boolean adjustOutputDownwardsForFee(
Coin feePerKb,
boolean ensureMinRequiredFee
) {
- final int size = calculateTxSize(tx, isSegwit, coinSelection);
+ final int size = calculateTxSizeForFees(tx, isSegwit, coinSelection);
Coin fee = feePerKb.multiply(size).divide(1000);
if (ensureMinRequiredFee && fee.compareTo(BtcTransaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
fee = BtcTransaction.REFERENCE_DEFAULT_MIN_TX_FEE;
@@ -1007,7 +1021,7 @@ private FeeCalculation calculateFee(
checkState(input.getScriptBytes().length == 0);
}
- int size = calculateTxSize(tx, req.isSegwitCompatible, selection);
+ int size = calculateTxSizeForFees(tx, req.isSegwitCompatible, selection);
Coin feePerKb = req.feePerKb;
if (needAtLeastReferenceFee && feePerKb.compareTo(BtcTransaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) {
@@ -1026,32 +1040,53 @@ private FeeCalculation calculateFee(
return result;
}
- private int calculateTxSize(BtcTransaction tx, boolean isSegwitCompatible, CoinSelection selection) {
- int baseSize = calculateTxBaseSize(tx, isSegwitCompatible);
- int totalSize = baseSize + estimateBytesForSigning(selection);
-
+ /**
+ * Calculates the virtual size of a Bitcoin transaction for fee estimation purposes.
+ * When estimating fees, we assume the transaction is not yet signed, so we need to consider
+ * the expected size of the signatures.
+ * - If the transaction is legacy (non-SegWit), signature data is part of the base size,
+ * and the total size is equal to the base size.
+ * - If the transaction is SegWit-compatible, the scriptSig is a 36-byte fixed-size hash
+ * located in the input, so its part of the base size. Signatures are located in the
+ * witness, so they are just part of the total size.
+ * @param tx the unsigned Bitcoin transaction
+ * @param isSegwitCompatible whether the transaction is SegWit-compatible
+ * @param bestCoinSelection the selected UTXOs for the transaction
+ * @return the estimated virtual size of the transaction
+ */
+ private int calculateTxSizeForFees(BtcTransaction tx, boolean isSegwitCompatible, CoinSelection bestCoinSelection) {
+ int baseSize = tx.unsafeBitcoinSerialize().length;
+ int totalSize;
if (!isSegwitCompatible) {
- return totalSize;
+ baseSize += estimateBytesForSigning(bestCoinSelection);
+ totalSize = baseSize;
+ } else {
+ baseSize += calculateSegwitScriptSigSize(tx);
+ totalSize = baseSize;
+ totalSize += estimateBytesForSigning(bestCoinSelection);
}
-
- // As described in BIP141
- int txWeight = totalSize + (3 * baseSize);
- return txWeight / 4;
+ return calculateVirtualSize(baseSize, totalSize);
}
- private static int calculateTxBaseSize(BtcTransaction tx, boolean isSegwit) {
- int baseSize = tx.unsafeBitcoinSerialize().length;
- if (!isSegwit) {
- return baseSize;
- }
-
- // at this time the script sig for every input is empty.
- // in segwit-compatible, this is a 36-bytes-fixed-size hash,
- // so we should count its bytes manually.
+ private int calculateSegwitScriptSigSize(BtcTransaction tx) {
int segwitCompatibleScriptSigSize = 36;
- baseSize += tx.getInputs().size() * segwitCompatibleScriptSigSize;
+ return tx.getInputs().size() * segwitCompatibleScriptSigSize;
+ }
- return baseSize;
+ /**
+ * Calculates the virtual size of a Bitcoin transaction as defined in BIP141.
+ * The virtual size is a weighted size used to properly account for the discount SegWit
+ * provides on witness data.
+ * - For legacy transactions, {@code baseSize == totalSize}, so the virtual size equals both.
+ * - For SegWit transactions, {@code baseSize} excludes witness data, and {@code totalSize} includes it.
+ *
+ * @param baseSize the size of the transaction excluding witness data
+ * @param totalSize the full size of the transaction including witness data
+ * @return the virtual size in vbytes
+ */
+ private int calculateVirtualSize(int baseSize, int totalSize) {
+ int txWeight = totalSize + (3 * baseSize);
+ return txWeight / 4;
}
private void addSuppliedInputs(BtcTransaction tx, List originalInputs) {
diff --git a/src/test/java/co/rsk/bitcoinj/core/TransactionWitnessTest.java b/src/test/java/co/rsk/bitcoinj/core/TransactionWitnessTest.java
index 44bfccd45..772b50ce0 100644
--- a/src/test/java/co/rsk/bitcoinj/core/TransactionWitnessTest.java
+++ b/src/test/java/co/rsk/bitcoinj/core/TransactionWitnessTest.java
@@ -1,37 +1,67 @@
package co.rsk.bitcoinj.core;
+import co.rsk.bitcoinj.crypto.TransactionSignature;
+import co.rsk.bitcoinj.params.MainNetParams;
+import co.rsk.bitcoinj.script.RedeemScriptUtils;
import co.rsk.bitcoinj.script.Script;
import co.rsk.bitcoinj.script.ScriptBuilder;
import co.rsk.bitcoinj.script.ScriptOpCodes;
+import com.google.common.collect.Lists;
+import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
-import org.spongycastle.util.encoders.Hex;
+import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
public class TransactionWitnessTest {
- private static final Script redeemScript = new Script(
- Hex.decode("5221027de2af71862e0c64bf0ec5a66e3abc3b01fc57877802e6a6a81f6ea1d35610072102d9c67fef9f8d0707cbcca195eb5f26c6a65da6ca2d6130645c434bb924063856210346f033b8652a17d319d3ecbbbf20fd2cd663a6548173b9419d8228eef095012e53ae")
- ); // data from tx https://mempool.space/testnet/tx/1744459aeaf7369aadc9fc40de9ab2bf575b14e35029b35a7ee4bbd3de65af7f
- private static final byte[] redeemScriptHash = Sha256Hash.hash(redeemScript.getProgram());
-
+ private static final int FIRST_INPUT_INDEX = 0;
+ private static final NetworkParameters MAINNET_PARAMS = MainNetParams.get();
+ private static final List FEDERATION_KEYS = RedeemScriptUtils.getDefaultRedeemScriptKeys();
+ private static final BtcECKey fedKey1 = FEDERATION_KEYS.get(0);
+ private static final BtcECKey fedKey2 = FEDERATION_KEYS.get(1);
+ private static final BtcECKey fedKey3 = FEDERATION_KEYS.get(2);
+ private static final List ERP_FEDERATION_KEYS = RedeemScriptUtils.getEmergencyRedeemScriptKeys();
+ private static final long CSV_VALUE = 52_560L;
+ private static final Script redeemScript = RedeemScriptUtils.createP2shErpRedeemScript(
+ FEDERATION_KEYS,
+ ERP_FEDERATION_KEYS,
+ CSV_VALUE
+ );
+ private static final Sha256Hash redeemScriptSerialized = Sha256Hash.of(redeemScript.getProgram());
+ private static final Script p2shP2wshOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(redeemScript);
+ private static final int sigsPrefixCount = p2shP2wshOutputScript.getSigsPrefixCount();
private static final Script witnessScript = new ScriptBuilder()
.number(ScriptOpCodes.OP_0)
- .data(redeemScriptHash)
+ .data(redeemScriptSerialized.getBytes())
.build();
private static final byte[] witnessScriptHash = Utils.sha256hash160(witnessScript.getProgram());
-
private static final byte[] op0 = new byte[] {};
-
+ private static final Coin fundingValue = Coin.FIFTY_COINS;
+ private static final BtcTransaction fundingTx = getFundingBtcTransaction();
private List pushes;
+ private BtcTransaction btcTx;
+ private Sha256Hash btcTxSigHashForWitness;
+
+
+ @Before
+ public void setUp() {
+ pushes = new ArrayList<>();
+ btcTx = new BtcTransaction(MAINNET_PARAMS);
+ btcTx.addInput(fundingTx.getOutput(0));
+ btcTx.addInput(fundingTx.getOutput(1));
+ TransactionWitness witnessWithRedeemScript = createBaseWitnessThatSpendsFromErpRedeemScript(redeemScript);
+ btcTx.setWitness(FIRST_INPUT_INDEX, witnessWithRedeemScript);
+ btcTxSigHashForWitness = btcTx.hashForWitnessSignature(FIRST_INPUT_INDEX, redeemScript, fundingValue,
+ BtcTransaction.SigHash.ALL, false);
+ }
@Test
public void of_withValidPushes_createsTransactionWitnessWithPushes() {
// arrange
- pushes = new ArrayList<>();
-
pushes.add(op0);
pushes.add(witnessScriptHash);
@@ -47,8 +77,6 @@ public void of_withValidPushes_createsTransactionWitnessWithPushes() {
@Test
public void of_withOneNullPush_throwsNPE() {
// arrange
- pushes = new ArrayList<>();
-
pushes.add(op0);
pushes.add(witnessScriptHash);
@@ -163,6 +191,7 @@ public void equals_withTwoTransactionWitnessesWithTheSameElementsPushed_shouldBe
TransactionWitness transactionWitness2 = new TransactionWitness(1);
transactionWitness2.setPush(0, samePush);
+ // act
int hashCode1 = transactionWitness1.hashCode();
int hashCode2 = transactionWitness2.hashCode();
@@ -186,6 +215,7 @@ public void equals_withTwoTransactionWitnessesWithOneDifferentPush_shouldBeFalse
transactionWitness1.setPush(0, samePush);
transactionWitness2.setPush(0, anotherDifferentPush);
+ // act
int hashCode1 = transactionWitness1.hashCode();
int hashCode2 = transactionWitness2.hashCode();
@@ -193,4 +223,504 @@ public void equals_withTwoTransactionWitnessesWithOneDifferentPush_shouldBeFalse
assertNotEquals(transactionWitness1, transactionWitness2);
assertNotEquals(hashCode1, hashCode2);
}
+
+ @Test
+ public void getSigInsertionIndex_whenEmptyWitness_shouldThrownArrayIndexOutOfBoundsException() {
+ // arrange
+ Sha256Hash hashForSignature = Sha256Hash.of(new byte[]{1});
+ pushes = new ArrayList<>();
+ TransactionWitness transactionWitness = TransactionWitness.of(pushes);
+
+ // act & assert
+ assertThrows(ArrayIndexOutOfBoundsException.class, () -> transactionWitness.getSigInsertionIndex(hashForSignature, fedKey1));
+ }
+
+ @Test
+ public void getSigInsertionIndex_whenMalformedRedeemScript_shouldThrowException() {
+ // arrange
+ Sha256Hash hashForSignature = Sha256Hash.of(new byte[]{1});
+ Script customRedeemScript = new Script(new byte[2]);
+ byte[] emptyByte = {};
+ pushes = new ArrayList<>();
+ pushes.add(emptyByte);
+ pushes.add(customRedeemScript.getProgram());
+ TransactionWitness transactionWitness = TransactionWitness.of(pushes);
+
+ // act & assert
+ assertThrows(ScriptException.class, () -> transactionWitness.getSigInsertionIndex(hashForSignature, fedKey1));
+ }
+
+ @Test
+ public void getSigInsertionIndex_withWitnessWithoutSignatures_shouldReturnZeroForAllKeys() {
+ // arrange
+ TransactionWitness witness = btcTx.getWitness(FIRST_INPUT_INDEX);
+
+ // act
+ for (BtcECKey key: FEDERATION_KEYS) {
+ int sigInsertionIndex = witness.getSigInsertionIndex(btcTxSigHashForWitness, key);
+
+ // assert
+ Assert.assertEquals(0, sigInsertionIndex);
+ }
+ }
+
+ @Test
+ public void getSigInsertionIndex_withAGreaterPubKeySignatureInTheWitness_shouldReturnIndexCorrectly() {
+ // arrange
+ TransactionWitness witnessWithoutSignatures = btcTx.getWitness(FIRST_INPUT_INDEX);
+
+ // act & assert
+ int sigIndexForFedKey1BeforeSigning = witnessWithoutSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ int sigIndexForFedKey2BeforeSigning = witnessWithoutSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey2);
+ Assert.assertEquals(0, sigIndexForFedKey1BeforeSigning);
+ Assert.assertEquals(0, sigIndexForFedKey2BeforeSigning);
+
+ // sign with fedKey1
+ signInput(btcTx, fedKey1, FIRST_INPUT_INDEX, btcTxSigHashForWitness);
+ TransactionWitness witnessWithSignature = btcTx.getWitness(FIRST_INPUT_INDEX);
+ int sigIndexForFedKey1AfterSigning = witnessWithSignature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ int sigIndexForFedKey2AfterSigning = witnessWithSignature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey2);
+
+ Assert.assertEquals(1, sigIndexForFedKey1AfterSigning);
+ Assert.assertEquals(1, sigIndexForFedKey2AfterSigning);
+ }
+
+ @Test
+ public void getSigInsertionIndex_withALowerPubKeySignatureInTheWitness_shouldReturnIndexCorrectly() {
+ // arrange
+ // sign with fedKey2
+ signInput(btcTx, fedKey2, FIRST_INPUT_INDEX, btcTxSigHashForWitness);
+ TransactionWitness witnessWithSignature = btcTx.getWitness(FIRST_INPUT_INDEX);
+
+ // act
+ int sigIndexForFedKey1 = witnessWithSignature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+
+ // assert
+ Assert.assertEquals(0, sigIndexForFedKey1);
+ }
+
+ @Test
+ public void getSigInsertionIndex_withTheSamePubKeyWhichSignatureAlreadyInTheWitness_shouldReturnIndexCorrectly() {
+ // arrange
+ // sign with fedKey1
+ signInput(btcTx, fedKey1, FIRST_INPUT_INDEX, btcTxSigHashForWitness);
+ TransactionWitness witnessWithSignature = btcTx.getWitness(FIRST_INPUT_INDEX);
+
+ // act
+ int sigIndexAfterInsertingSignature = witnessWithSignature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+
+ // assert
+ assertEquals(1, sigIndexAfterInsertingSignature);
+ }
+
+ @Test
+ public void getSigInsertionIndex_withDifferentSignaturesInTheWitness_shouldReturnIndexCorrectly() {
+ // arrange
+ // sign with fedKey2
+ signInput(btcTx, fedKey2, FIRST_INPUT_INDEX, btcTxSigHashForWitness);
+ TransactionWitness witnessWithSignature = btcTx.getWitness(FIRST_INPUT_INDEX);
+
+ // act & assert
+ int sigIndexForFedKey1 = witnessWithSignature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ int sigIndexForFedKey3 = witnessWithSignature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey3);
+
+ Assert.assertEquals(0, sigIndexForFedKey1);
+ // fedKey3 should be inserted after fedKey2
+ Assert.assertEquals(1, sigIndexForFedKey3);
+
+ // now fedKey1 signs the input and pushes fedKey2's signature one position
+ signInput(btcTx, fedKey1, FIRST_INPUT_INDEX, btcTxSigHashForWitness);
+ TransactionWitness witnessWithFedKey1Signature = btcTx.getWitness(FIRST_INPUT_INDEX);
+
+ sigIndexForFedKey3 = witnessWithFedKey1Signature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey3);
+ Assert.assertEquals(2, sigIndexForFedKey3);
+ }
+
+ @Test
+ public void getSigInsertionIndex_withFedKey3SignatureInTheWitness_shouldReturnIndexCorrectly() {
+ // arrange
+ // sign with fedKey3
+ signInput(btcTx, fedKey3, FIRST_INPUT_INDEX, btcTxSigHashForWitness);
+ TransactionWitness witnessWithFedKey3Signature = btcTx.getWitness(FIRST_INPUT_INDEX);
+
+ // act
+ int sigIndexForFedKey1 = witnessWithFedKey3Signature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ int sigIndexForFedKey2 = witnessWithFedKey3Signature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey2);
+
+ // assert
+ Assert.assertEquals(0, sigIndexForFedKey1);
+ Assert.assertEquals(0, sigIndexForFedKey2);
+ }
+
+ @Test
+ public void getSigInsertionIndex_withWitnessFilledWithSignatures_shouldReturnTheProperIndex() {
+ // arrange
+ int i = 0;
+ while(i < redeemScript.getNumberOfSignaturesRequiredToSpend()) {
+ BtcECKey key = FEDERATION_KEYS.get(i);
+ signInput(btcTx, key, FIRST_INPUT_INDEX, btcTxSigHashForWitness);
+ i++;
+ }
+
+ TransactionWitness signedTransactionWitness = btcTx.getWitness(FIRST_INPUT_INDEX);
+ BtcECKey key = FEDERATION_KEYS.get(i);
+
+ // act
+ int sigInsertionIndex = signedTransactionWitness.getSigInsertionIndex(btcTxSigHashForWitness, key);
+
+ //assert
+ assertEquals(i, sigInsertionIndex);
+ }
+
+ @Test
+ public void updateWitnessWithSignature_withOneSignature_shouldReturnAWitnessWithTheSignaturePlacedCorrectly() {
+ // signing order: [fedKey1]
+ // arrange
+ byte[] signatureEncodeToBitcoin = getTransactionSignatureEncodedToBtc(fedKey1, btcTxSigHashForWitness);
+
+ TransactionWitness witnessWithoutSignature = btcTx.getWitness(FIRST_INPUT_INDEX);
+ int sigIndex = witnessWithoutSignature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ assertEquals(0, sigIndex);
+
+ // act
+ TransactionWitness witnessWithOneSignature = witnessWithoutSignature.updateWitnessWithSignature(
+ p2shP2wshOutputScript, signatureEncodeToBitcoin, sigIndex);
+
+ // assert
+ assertSignaturesAreInOrder(witnessWithOneSignature, Lists.newArrayList(signatureEncodeToBitcoin));
+ }
+
+ @Test
+ public void updateWitnessWithSignature_twoTimesWithTheSameSignature_shouldInsertBoth() {
+ // signing order: [fedKey1, fedKey1]
+ // expected signatures order: [signatureFed1, signatureFed1]
+ // arrange
+ byte[] fedKey1TxSignature = getTransactionSignatureEncodedToBtc(fedKey1, btcTxSigHashForWitness);
+ TransactionWitness witnessWithoutSignatures = btcTx.getWitness(FIRST_INPUT_INDEX);
+
+ // act & assert
+ int sigIndex = witnessWithoutSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ assertEquals(0, sigIndex);
+ TransactionWitness witnessWithOneSignature = witnessWithoutSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey1TxSignature, sigIndex);
+ assertSignaturesAreInOrder(witnessWithOneSignature, Lists.newArrayList(fedKey1TxSignature));
+
+ int sigIndexAfterSigning = witnessWithOneSignature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ assertNotEquals(sigIndex, sigIndexAfterSigning);
+ assertEquals(1, sigIndexAfterSigning);
+
+ TransactionWitness witnessWithTwoSignatures = witnessWithOneSignature.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey1TxSignature, sigIndexAfterSigning);
+
+ assertSignaturesAreInOrder(witnessWithTwoSignatures, Lists.newArrayList(fedKey1TxSignature, fedKey1TxSignature));
+ }
+
+ @Test
+ public void updateWitnessWithSignature_withWitnessFilledWithSignatures_shouldThrowAnError() {
+ // signing order: [fedKey1, .., maxFedKey, fedKey1] signatures
+ // expected signatures order: [signatureFed1, .., signatureMaxFed]
+ // arrange
+ int i = 0;
+ int numberOfSignaturesRequiredToSpend = redeemScript.getNumberOfSignaturesRequiredToSpend();
+ while(i < numberOfSignaturesRequiredToSpend) {
+ BtcECKey key = FEDERATION_KEYS.get(i);
+ signInput(btcTx, key, FIRST_INPUT_INDEX, btcTxSigHashForWitness);
+ i++;
+ }
+
+ // getSuffixCount doesn't consider OP_NOTIF param op code, so the calculation for
+ // the number of signatures required in updateWitnessWithSignature is
+ // wrong.
+ BtcECKey key = FEDERATION_KEYS.get(i++);
+ signInput(btcTx, key, FIRST_INPUT_INDEX, btcTxSigHashForWitness);
+
+ byte[] txSig = getTransactionSignatureEncodedToBtc(fedKey1, btcTxSigHashForWitness);
+ TransactionWitness signedTransactionWitness = btcTx.getWitness(FIRST_INPUT_INDEX);
+ key = FEDERATION_KEYS.get(i);
+ int sigInsertionIndex = signedTransactionWitness.getSigInsertionIndex(btcTxSigHashForWitness, key);
+
+ // act & assert
+ assertTrue(numberOfSignaturesRequiredToSpend < sigInsertionIndex);
+ assertEquals(numberOfSignaturesRequiredToSpend + 1, sigInsertionIndex);
+
+ // It fails because the witness is already filled with signatures.
+ // Then, the sigIndex is higher than the amount of signatures required.
+ assertThrows(IllegalArgumentException.class, () -> signedTransactionWitness.updateWitnessWithSignature(
+ p2shP2wshOutputScript, txSig, sigInsertionIndex));
+ }
+
+ @Test
+ public void updateWitnessWithSignature_withTheLowestSignatureInWitness_shouldInsertTheNewOneAsSecond() {
+ // signing order: [fedKey1, fedKey2]
+ // expected signatures order: [signatureFed1, signatureFed2]
+ byte[] fedKey1SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey1, btcTxSigHashForWitness);
+ TransactionWitness witnessWithoutSignatures = btcTx.getWitness(FIRST_INPUT_INDEX);
+ int fed1SigInsertionIndex = witnessWithoutSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ assertEquals(0, fed1SigInsertionIndex);
+
+ // act & assert
+ TransactionWitness witnessWithFedKey1Signature = witnessWithoutSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey1SignatureEncoded, fed1SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithFedKey1Signature, Lists.newArrayList(fedKey1SignatureEncoded));
+
+ byte[] fedKey2TxSignature = getTransactionSignatureEncodedToBtc(fedKey1, btcTxSigHashForWitness);
+ int fed2SigInsertionIndex = witnessWithFedKey1Signature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey2);
+ assertEquals(1, fed2SigInsertionIndex);
+ TransactionWitness witnessWithTwoSignatures = witnessWithFedKey1Signature.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey2TxSignature, fed2SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithTwoSignatures, Lists.newArrayList(fedKey1SignatureEncoded, fedKey1SignatureEncoded));
+ }
+
+ @Test
+ public void updateWitnessWithSignature_withALowerSignatureInWitness_shouldInsertTheNewOneAsFirst() {
+ // signing order: [fedKey2, fedKey1]
+ // expected signatures order: [signatureFed1, signatureFed2]
+ // arrange
+ byte[] fedKey2SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey2, btcTxSigHashForWitness);
+ TransactionWitness witnessWithoutSignatures = btcTx.getWitness(FIRST_INPUT_INDEX);
+
+ // act & assert
+ int fed2SigInsertionIndex = witnessWithoutSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey2);
+ assertEquals(0, fed2SigInsertionIndex);
+ TransactionWitness witnessWithFedKey2Signature = witnessWithoutSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey2SignatureEncoded, fed2SigInsertionIndex);
+ assertSignaturesAreInOrder(witnessWithFedKey2Signature, Lists.newArrayList(fedKey2SignatureEncoded));
+
+ byte[] fedKey1SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey1, btcTxSigHashForWitness);
+ int fed1SigInsertionIndex = witnessWithFedKey2Signature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ assertEquals(0, fed1SigInsertionIndex);
+ TransactionWitness witnessWithTwoSignatures = witnessWithFedKey2Signature.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey1SignatureEncoded, fed1SigInsertionIndex);
+
+ ArrayList expectedSignatures = Lists.newArrayList(fedKey1SignatureEncoded, fedKey2SignatureEncoded);
+ assertSignaturesAreInOrder(witnessWithTwoSignatures, expectedSignatures);
+ }
+
+ @Test
+ public void updateWitnessWithSignature_withThreeSignaturesInDescendingOrder_shouldBeInsertedRespectingTheOrder() {
+ // signing order: [fedKey3, fedKey2, fedKey1]
+ // expected signatures order: [signatureFed1, signatureFed2, signatureFed3]
+ // arrange
+ byte[] fedKey3SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey3, btcTxSigHashForWitness);
+ TransactionWitness witnessWithoutSignatures = btcTx.getWitness(FIRST_INPUT_INDEX);
+
+ // act & assert
+ int fed3SigInsertionIndex = witnessWithoutSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey3);
+ assertEquals(0, fed3SigInsertionIndex);
+ TransactionWitness witnessWithFedKey3Signature = witnessWithoutSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey3SignatureEncoded, fed3SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithFedKey3Signature, Lists.newArrayList(fedKey3SignatureEncoded));
+
+ byte[] fedKey2SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey2, btcTxSigHashForWitness);
+ int fed2SigInsertionIndex = witnessWithFedKey3Signature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey2);
+ assertEquals(0, fed2SigInsertionIndex);
+ TransactionWitness witnessWithTwoSignatures = witnessWithFedKey3Signature.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey2SignatureEncoded, fed2SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithTwoSignatures, Lists.newArrayList(fedKey2SignatureEncoded, fedKey3SignatureEncoded));
+
+ byte[] fedKey1SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey1, btcTxSigHashForWitness);
+ int fed1SigInsertionIndex = witnessWithTwoSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ assertEquals(0, fed1SigInsertionIndex);
+ TransactionWitness witnessWithThreeSignatures = witnessWithTwoSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey1SignatureEncoded, fed1SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithThreeSignatures, Lists.newArrayList(fedKey1SignatureEncoded, fedKey2SignatureEncoded, fedKey3SignatureEncoded));
+ }
+
+ @Test
+ public void updateWitnessWithSignature_withThreeUnorderedSignatures_shouldBeInsertedRespectingTheOrder() {
+ // signing order: [fedKey1, fedKey3, fedKey2]
+ // expected signatures order: [signatureFed1, signatureFed2, signatureFed3]
+ // arrange
+ byte[] fedKey1SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey1, btcTxSigHashForWitness);
+ TransactionWitness witnessWithoutSignatures = btcTx.getWitness(FIRST_INPUT_INDEX);
+
+ // act & assert
+ int fed1SigInsertionIndex = witnessWithoutSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ assertEquals(0, fed1SigInsertionIndex);
+ TransactionWitness witnessWithFedKey1Signature = witnessWithoutSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey1SignatureEncoded, fed1SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithFedKey1Signature, Lists.newArrayList(fedKey1SignatureEncoded));
+
+ byte[] fedKey3SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey3, btcTxSigHashForWitness);
+ int fed3SigInsertionIndex = witnessWithFedKey1Signature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey3);
+ assertEquals(1, fed3SigInsertionIndex);
+ TransactionWitness witnessWithTwoSignatures = witnessWithFedKey1Signature.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey3SignatureEncoded, fed3SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithTwoSignatures, Lists.newArrayList(fedKey1SignatureEncoded,
+ fedKey3SignatureEncoded));
+
+ byte[] fedKey2SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey2, btcTxSigHashForWitness);
+ int fed2SigInsertionIndex = witnessWithTwoSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey2);
+ assertEquals(1, fed2SigInsertionIndex);
+ TransactionWitness witnessWithThreeSignatures = witnessWithTwoSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey2SignatureEncoded, fed2SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithThreeSignatures, Lists.newArrayList(fedKey1SignatureEncoded,
+ fedKey2SignatureEncoded, fedKey3SignatureEncoded));
+ }
+
+ @Test
+ public void updateWitnessWithSignature_withThreeSignaturesInAscendingOrder_shouldBeInsertedRespectingTheOrder() {
+ // signing order: [fedKey1, fedKey2, fedKey3]
+ // expected signatures order: [signatureFed1, signatureFed2, signatureFed3]
+ // arrange
+ byte[] fedKey1SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey1, btcTxSigHashForWitness);
+ TransactionWitness witnessWithoutSignatures = btcTx.getWitness(FIRST_INPUT_INDEX);
+ int fed1SigInsertionIndex = witnessWithoutSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ assertEquals(0, fed1SigInsertionIndex);
+ // act & assert
+ TransactionWitness witnessWithFedKey1Signature = witnessWithoutSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey1SignatureEncoded, fed1SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithFedKey1Signature, Lists.newArrayList(fedKey1SignatureEncoded));
+
+ byte[] fedKey2SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey2, btcTxSigHashForWitness);
+ int fed2SigInsertionIndex = witnessWithFedKey1Signature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey2);
+ assertEquals(1, fed2SigInsertionIndex);
+ TransactionWitness witnessWithTwoSignatures = witnessWithFedKey1Signature.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey2SignatureEncoded, fed2SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithTwoSignatures, Lists.newArrayList(fedKey1SignatureEncoded,
+ fedKey2SignatureEncoded));
+
+ byte[] fedKey3SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey3, btcTxSigHashForWitness);
+ int fed3SigInsertionIndex = witnessWithTwoSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey3);
+ assertEquals(2, fed3SigInsertionIndex);
+ TransactionWitness witnessWithThreeSignatures = witnessWithTwoSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey3SignatureEncoded, fed3SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithThreeSignatures, Lists.newArrayList(fedKey1SignatureEncoded,
+ fedKey2SignatureEncoded, fedKey3SignatureEncoded));
+ }
+
+ @Test
+ public void updateWitnessWithSignature_withThreeSignaturesInADifferentOrder_shouldBeInsertedRespectingTheOrder() {
+ // signing order: [fedKey2, fedKey1, fedKey3]
+ // expected signatures order: [signatureFed1, signatureFed2, signatureFed3]
+ // arrange
+ byte[] fedKey2SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey2, btcTxSigHashForWitness);
+ TransactionWitness witnessWithoutSignatures = btcTx.getWitness(FIRST_INPUT_INDEX);
+ int fed2SigInsertionIndex = witnessWithoutSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey2);
+ assertEquals(0, fed2SigInsertionIndex);
+ // act & assert
+ TransactionWitness witnessWithFedKey2Signature = witnessWithoutSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey2SignatureEncoded, fed2SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithFedKey2Signature, Lists.newArrayList(fedKey2SignatureEncoded));
+
+ byte[] fedKey1SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey1, btcTxSigHashForWitness);
+ int fed1SigInsertionIndex = witnessWithFedKey2Signature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ TransactionWitness witnessWithTwoSignatures = witnessWithFedKey2Signature.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey1SignatureEncoded, fed1SigInsertionIndex);
+ assertEquals(0, fed1SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithTwoSignatures, Lists.newArrayList(fedKey1SignatureEncoded,
+ fedKey2SignatureEncoded));
+
+ byte[] fedKey3SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey3, btcTxSigHashForWitness);
+ int fed3SigInsertionIndex = witnessWithTwoSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey3);
+ assertEquals(2, fed3SigInsertionIndex);
+ TransactionWitness witnessWithThreeSignatures = witnessWithTwoSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey3SignatureEncoded, fed3SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithThreeSignatures, Lists.newArrayList(fedKey1SignatureEncoded,
+ fedKey2SignatureEncoded, fedKey3SignatureEncoded));
+ }
+
+ @Test
+ public void updateWitnessWithSignature_withThreeSignaturesInASecondDifferentOrder_shouldBeInsertedRespectingTheOrder() {
+ // signing order: [fedKey3, fedKey1, fedKey2]
+ // expected signatures order: [signatureFed1, signatureFed2, signatureFed3]
+ // arrange
+ byte[] fedKey3SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey3, btcTxSigHashForWitness);
+ TransactionWitness witnessWithoutSignatures = btcTx.getWitness(FIRST_INPUT_INDEX);
+ int fed3SigInsertionIndex = witnessWithoutSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey3);
+ assertEquals(0, fed3SigInsertionIndex);
+ // act & assert
+ TransactionWitness witnessWithFedKey3Signature = witnessWithoutSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey3SignatureEncoded, fed3SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithFedKey3Signature, Lists.newArrayList(fedKey3SignatureEncoded));
+
+ byte[] fedKey1SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey1, btcTxSigHashForWitness);
+ int fed1SigInsertionIndex = witnessWithFedKey3Signature.getSigInsertionIndex(btcTxSigHashForWitness, fedKey1);
+ assertEquals(0, fed1SigInsertionIndex);
+ TransactionWitness witnessWithTwoSignatures = witnessWithFedKey3Signature.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey1SignatureEncoded, fed1SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithTwoSignatures, Lists.newArrayList(fedKey1SignatureEncoded,
+ fedKey3SignatureEncoded));
+
+ byte[] fedKey2SignatureEncoded = getTransactionSignatureEncodedToBtc(fedKey2, btcTxSigHashForWitness);
+ int fed2SigInsertionIndex = witnessWithTwoSignatures.getSigInsertionIndex(btcTxSigHashForWitness, fedKey2);
+ assertEquals(1, fed2SigInsertionIndex);
+ TransactionWitness witnessWithThreeSignatures = witnessWithTwoSignatures.updateWitnessWithSignature(
+ p2shP2wshOutputScript, fedKey2SignatureEncoded, fed2SigInsertionIndex);
+
+ assertSignaturesAreInOrder(witnessWithThreeSignatures, Lists.newArrayList(fedKey1SignatureEncoded,
+ fedKey2SignatureEncoded, fedKey3SignatureEncoded));
+ }
+
+ private void assertSignaturesAreInOrder(TransactionWitness witness, List expectedSignatures) {
+ int index = 0;
+ for (byte[] expectedSignature : expectedSignatures) {
+ int signaturePosition = sigsPrefixCount + index;
+ byte[] actualSignature = witness.getPush(signaturePosition);
+ assertArrayEquals(expectedSignature, actualSignature);
+ index++;
+ }
+ }
+
+ private byte[] getTransactionSignatureEncodedToBtc(BtcECKey key, Sha256Hash sigHash) {
+ byte[] federatorSig = key.sign(sigHash).encodeToDER();
+ BtcECKey.ECDSASignature signature = BtcECKey.ECDSASignature.decodeFromDER(federatorSig);
+ TransactionSignature txSig = new TransactionSignature(signature, BtcTransaction.SigHash.ALL, false);
+ return txSig.encodeToBitcoin();
+ }
+
+ private static BtcTransaction getFundingBtcTransaction() {
+ BtcTransaction btcTx = new BtcTransaction(MAINNET_PARAMS);
+ final Address userAddress = BtcECKey.fromPrivate(BigInteger.valueOf(901)).toAddress(MAINNET_PARAMS);
+ btcTx.addOutput(fundingValue, userAddress);
+ btcTx.addOutput(fundingValue, userAddress);
+ return btcTx;
+ }
+
+ private void signInput(BtcTransaction btcTx, BtcECKey key, int inputIndex, Sha256Hash sigHash) {
+ byte[] txSigEncodedForBitcoin = getTransactionSignatureEncodedToBtc(key, sigHash);
+
+ TransactionWitness transactionWitness = btcTx.getWitness(inputIndex);
+ int sigIndex = transactionWitness.getSigInsertionIndex(sigHash, key);
+ TransactionWitness witnessWithSignature = transactionWitness.updateWitnessWithSignature(p2shP2wshOutputScript,
+ txSigEncodedForBitcoin, sigIndex);
+ btcTx.setWitness(inputIndex, witnessWithSignature);
+ }
+
+ private static TransactionWitness createBaseWitnessThatSpendsFromErpRedeemScript(Script redeemScript) {
+ int pushForEmptyByte = 1;
+ int pushForOpNotif = 1;
+ int pushForRedeemScript = 1;
+ int numberOfSignaturesRequiredToSpend = redeemScript.getNumberOfSignaturesRequiredToSpend();
+ int witnessSize = pushForRedeemScript + pushForOpNotif + numberOfSignaturesRequiredToSpend + pushForEmptyByte;
+
+ List pushes = new ArrayList<>(witnessSize);
+ byte[] emptyByte = {};
+ pushes.add(emptyByte); // OP_0
+
+ for (int i = 0; i < numberOfSignaturesRequiredToSpend; i++) {
+ pushes.add(emptyByte);
+ }
+
+ byte[] opNotIf = {};
+ pushes.add(opNotIf);
+ pushes.add(redeemScript.getProgram());
+ return TransactionWitness.of(pushes);
+ }
}
diff --git a/src/test/java/co/rsk/bitcoinj/script/FlyoverRedeemScriptParserTest.java b/src/test/java/co/rsk/bitcoinj/script/FlyoverRedeemScriptParserTest.java
index 0bd23a5fa..843331d96 100644
--- a/src/test/java/co/rsk/bitcoinj/script/FlyoverRedeemScriptParserTest.java
+++ b/src/test/java/co/rsk/bitcoinj/script/FlyoverRedeemScriptParserTest.java
@@ -78,14 +78,14 @@ public void getM_whenFlyoverRedeemScriptContainsP2shErpRedeemScript_shouldReturn
private void assertGetMValue(Script flyoverRedeemScript) {
// Arrange
- final int EXPECTED_M = 5;
+ int expectedNumberOfSignatures = keys.size() / 2 + 1;
FlyoverRedeemScriptParser flyoverRedeemScriptParser = new FlyoverRedeemScriptParser(flyoverRedeemScript.getChunks());
// Act
- int actualM = flyoverRedeemScriptParser.getM();
+ int actualNumberOfSignatures = flyoverRedeemScriptParser.getM();
// Assert
- assertEquals(EXPECTED_M, actualM);
+ assertEquals(expectedNumberOfSignatures, actualNumberOfSignatures);
}
@Test
@@ -129,7 +129,7 @@ public void findKeyInRedeem_whenKeyIsNotInP2shErpRedeemScript_shouldThrowIllegal
private void assertThrowsIllegalStateException(Script flyoverRedeemScript) {
// Arrange
- final BtcECKey differentKey = BtcECKey.fromPrivate(BigInteger.valueOf(1000));
+ final BtcECKey differentKey = BtcECKey.fromPrivate(BigInteger.valueOf(10001));
FlyoverRedeemScriptParser flyoverRedeemScriptParser = new FlyoverRedeemScriptParser(flyoverRedeemScript.getChunks());
// Act - Assert
diff --git a/src/test/java/co/rsk/bitcoinj/script/RedeemScriptUtils.java b/src/test/java/co/rsk/bitcoinj/script/RedeemScriptUtils.java
index 39868d5b0..0d1524a75 100644
--- a/src/test/java/co/rsk/bitcoinj/script/RedeemScriptUtils.java
+++ b/src/test/java/co/rsk/bitcoinj/script/RedeemScriptUtils.java
@@ -5,6 +5,7 @@
import co.rsk.bitcoinj.core.BtcECKey;
import co.rsk.bitcoinj.core.Utils;
import java.math.BigInteger;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -102,22 +103,23 @@ public static Script createFlyoverRedeemScript(byte[] derivationArgumentsHashByt
}
public static List getDefaultRedeemScriptKeys() {
- List keys = Arrays.asList(
- BtcECKey.fromPrivate(BigInteger.valueOf(100)),
- BtcECKey.fromPrivate(BigInteger.valueOf(200)),
- BtcECKey.fromPrivate(BigInteger.valueOf(300)),
- BtcECKey.fromPrivate(BigInteger.valueOf(400)),
- BtcECKey.fromPrivate(BigInteger.valueOf(500)),
- BtcECKey.fromPrivate(BigInteger.valueOf(600)),
- BtcECKey.fromPrivate(BigInteger.valueOf(700)),
- BtcECKey.fromPrivate(BigInteger.valueOf(800)),
- BtcECKey.fromPrivate(BigInteger.valueOf(900))
- );
+ List keys = getNKeys(20);
keys.sort(BtcECKey.PUBKEY_COMPARATOR);
return keys;
}
+ public static List getNKeys(int n) {
+ ArrayList keys = new ArrayList<>(n);
+ for (int i = 1; i <= n; i++) {
+ long seed = i * 100;
+ BtcECKey btcECKey = BtcECKey.fromPrivate(BigInteger.valueOf(seed));
+ keys.add(btcECKey);
+ }
+
+ return keys;
+ }
+
public static List getEmergencyRedeemScriptKeys() {
List keys = Arrays.asList(
BtcECKey.fromPrivate(BigInteger.valueOf(101)),
diff --git a/src/test/java/co/rsk/bitcoinj/script/RedeemScriptValidatorTest.java b/src/test/java/co/rsk/bitcoinj/script/RedeemScriptValidatorTest.java
index fa0363fc6..30d5b89af 100644
--- a/src/test/java/co/rsk/bitcoinj/script/RedeemScriptValidatorTest.java
+++ b/src/test/java/co/rsk/bitcoinj/script/RedeemScriptValidatorTest.java
@@ -23,10 +23,11 @@ public class RedeemScriptValidatorTest {
private final BtcECKey ecKey1 = BtcECKey.fromPrivate(BigInteger.valueOf(110));
private final BtcECKey ecKey2 = BtcECKey.fromPrivate(BigInteger.valueOf(220));
private final BtcECKey ecKey3 = BtcECKey.fromPrivate(BigInteger.valueOf(330));
+ private final BtcECKey ecKey4 = BtcECKey.fromPrivate(BigInteger.valueOf(440));
private Script standardRedeemScript;
private Script flyoverStandardRedeemScript;
- private Script nonstandardErpRedeemScript;
+ private Script nonStandardErpRedeemScript;
private Script flyoverNonStandardErpRedeemScript;
private Script p2shErpRedeemScript;
private Script flyoverP2shErpRedeemScript;
@@ -42,11 +43,11 @@ public void setUp() {
flyoverStandardRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
FLYOVER_DERIVATION_HASH.getBytes(), standardRedeemScript);
- nonstandardErpRedeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
+ nonStandardErpRedeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
defaultRedeemScriptKeys, emergencyRedeemScriptKeys, CSV_VALUE);
flyoverNonStandardErpRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
FLYOVER_DERIVATION_HASH.getBytes(),
- nonstandardErpRedeemScript);
+ nonStandardErpRedeemScript);
p2shErpRedeemScript = RedeemScriptUtils.createP2shErpRedeemScript(defaultRedeemScriptKeys,
emergencyRedeemScriptKeys, CSV_VALUE);
@@ -59,8 +60,8 @@ public void setUp() {
@Test
public void isRedeemLikeScript_whenStandardMultisig_shouldReturnTrue() {
- List chunks = RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys).getChunks();
- Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(chunks));
+ List standardRedeemScriptChunks = standardRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(standardRedeemScriptChunks));
}
@Test
@@ -73,65 +74,37 @@ public void isRedeemLikeScript_whenNonStandardErpRedeemScriptParserHardcoded_sho
@Test
public void isRedeemLikeScript_whenNonStandardErpRedeemScriptParser_shouldReturnTrue() {
- List nonStandardErpRedeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys, CSV_VALUE).getChunks();
- Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(nonStandardErpRedeemScript));
+ List nonStandardErpRedeemScriptChunks = nonStandardErpRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(nonStandardErpRedeemScriptChunks));
}
@Test
public void isRedeemLikeScript_whenP2shErpRedeemScriptParserHardcoded_shouldReturnTrue() {
- List p2shRedeemScriptChunks = RedeemScriptUtils.createP2shErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys, CSV_VALUE).getChunks();
-
- Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(p2shRedeemScriptChunks));
+ List p2shErpRedeemScriptChunks = p2shErpRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(p2shErpRedeemScriptChunks));
}
@Test
public void isRedeemLikeScript_whenFlyoverStandardMultisigRedeemScript_shouldReturnTrue() {
- Script standardRedeemScript = RedeemScriptUtils.createStandardRedeemScript(
- defaultRedeemScriptKeys);
- List flyoverRedeemScriptChunks = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- standardRedeemScript
- ).getChunks();
-
- Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(flyoverRedeemScriptChunks));
+ List flyoverStandardRedeemScriptChunks = flyoverStandardRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(flyoverStandardRedeemScriptChunks));
}
@Test
public void isRedeemLikeScript_whenFlyoverNonStandardErpRedeemScript_shouldReturnTrue() {
- Script nonStandardErpRedeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys, CSV_VALUE);
- List flyoverRedeemScriptChunks = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- nonStandardErpRedeemScript
- ).getChunks();
-
- Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(flyoverRedeemScriptChunks));
+ List flyoverNonStandardErpRedeemScriptChunks = flyoverNonStandardErpRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(flyoverNonStandardErpRedeemScriptChunks));
}
@Test
public void isRedeemLikeScript_whenFlyoverP2shErpRedeemScript_shouldReturnTrue() {
- Script p2shErpRedeemScript = RedeemScriptUtils.createP2shErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys, CSV_VALUE);
- List p2shRedeemScriptChunks = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- p2shErpRedeemScript
- ).getChunks();
-
- Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(p2shRedeemScriptChunks));
+ List flyoverP2shErpRedeemScriptChunks = flyoverP2shErpRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.isRedeemLikeScript(flyoverP2shErpRedeemScriptChunks));
}
@Test
public void isRedeemLikeScript_invalid_redeem_script_missing_checkSig() {
- List chunksWithoutCheckSig = RedeemScriptValidator.removeOpCheckMultisig(
- RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys)
- );
-
+ List chunksWithoutCheckSig = RedeemScriptValidator.removeOpCheckMultisig(standardRedeemScript);
Assert.assertFalse(RedeemScriptValidator.isRedeemLikeScript(chunksWithoutCheckSig));
}
@@ -144,24 +117,95 @@ public void isRedeemLikeScript_invalid_redeem_script_insufficient_chunks() {
.data(ecKey3.getPubKey())
.build();
- Assert.assertFalse(RedeemScriptValidator.isRedeemLikeScript(redeemScript.getChunks()));
+ List redeemScriptChunks = redeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.isRedeemLikeScript(redeemScriptChunks));
}
@Test
public void hasStandardRedeemScriptStructure_standard_redeem_script() {
- Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys);
- Assert.assertTrue(RedeemScriptValidator.hasStandardRedeemScriptStructure(redeemScript.getChunks()));
+ List standardRedeemScriptChunks = standardRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasStandardRedeemScriptStructure(standardRedeemScriptChunks));
}
@Test
- public void hasStandardRedeemScriptStructure_non_standard_redeem_script() {
- Script redeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys,
- CSV_VALUE
- );
+ public void hasStandardRedeemScriptStructure_with21defaultRedeemScriptKeys_shoulThrowIAE() {
+ List bigNumberOfDefaultRedeemScriptKeys = RedeemScriptUtils.getNKeys(21);
+ Assert.assertThrows(IllegalArgumentException.class, () -> RedeemScriptUtils.createStandardRedeemScript(bigNumberOfDefaultRedeemScriptKeys));
+ }
- Assert.assertFalse(RedeemScriptValidator.hasStandardRedeemScriptStructure(redeemScript.getChunks()));
+ @Test
+ public void hasStandardRedeemScriptStructure_withLessKeysPushedThanExpected_shouldBeFalse() {
+ ScriptBuilder builder = new ScriptBuilder();
+ Script redeemScript = builder
+ .op(ScriptOpCodes.OP_4)
+ .data(ecKey1.getPubKey())
+ .op(ScriptOpCodes.OP_4)
+ .op(ScriptOpCodes.OP_CHECKMULTISIG)
+ .build();
+ List redeemScriptChunks = redeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasStandardRedeemScriptStructure(redeemScriptChunks));
+ }
+
+ @Test
+ public void hasStandardRedeemScriptStructure_withNegativeThreshold_shouldBeFalse() {
+ for (int n=0; n<20; n++) {
+ int i = -(1 << n); // i = -(2^n)
+ ScriptBuilder builder = new ScriptBuilder();
+ Script redeemScript = builder
+ .number(i)
+ .data(ecKey1.getPubKey())
+ .data(ecKey2.getPubKey())
+ .data(ecKey3.getPubKey())
+ .data(ecKey4.getPubKey())
+ .op(ScriptOpCodes.OP_4)
+ .op(ScriptOpCodes.OP_CHECKMULTISIG)
+ .build();
+
+ List redeemScriptChunks = redeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasStandardRedeemScriptStructure(redeemScriptChunks));
+ }
+ }
+
+ @Test
+ public void hasStandardRedeemScriptStructure_withNegativeTotalKeys_shouldBeFalse() {
+ for (int n=0; n<20; n++) {
+ int i = -(1 << n); // i = -(2^n)
+ ScriptBuilder builder = new ScriptBuilder();
+ Script redeemScript = builder
+ .op(ScriptOpCodes.OP_4)
+ .data(ecKey1.getPubKey())
+ .data(ecKey2.getPubKey())
+ .data(ecKey3.getPubKey())
+ .data(ecKey4.getPubKey())
+ .number(i)
+ .op(ScriptOpCodes.OP_CHECKMULTISIG)
+ .build();
+
+ List redeemScriptChunks = redeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasStandardRedeemScriptStructure(redeemScriptChunks));
+ }
+ }
+
+ @Test
+ public void hasStandardRedeemScriptStructure_withARedeemLikeScript_withoutOpCheckMultisigAsLastOpcode_shouldBeFalse() {
+ ScriptBuilder builder = new ScriptBuilder();
+ Script redeemScript = builder
+ .op(ScriptOpCodes.OP_2)
+ .data(ecKey1.getPubKey())
+ .data(ecKey2.getPubKey())
+ .data(ecKey3.getPubKey())
+ .op(ScriptOpCodes.OP_CHECKMULTISIG)
+ .op(ScriptOpCodes.OP_ENDIF)
+ .build();
+
+ List redeemScriptChunks = redeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasStandardRedeemScriptStructure(redeemScriptChunks));
+ }
+
+ @Test
+ public void hasStandardRedeemScriptStructure_non_standard_redeem_script() {
+ List nonStandardErpRedeemScriptChunks = nonStandardErpRedeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasStandardRedeemScriptStructure(nonStandardErpRedeemScriptChunks));
}
@Test
@@ -170,22 +214,26 @@ public void hasNonStandardErpRedeemScriptStructure_whenCustomRedeemScript_should
defaultRedeemScriptKeys
);
- Assert.assertFalse(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(customRedeemScript.getChunks()));
+ List customRedeemScriptChunks = customRedeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(customRedeemScriptChunks));
}
@Test
public void hasNonStandardErpRedeemScriptStructure_whenStandardRedeemScript_shouldReturnFalse() {
- Assert.assertFalse(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(standardRedeemScript.getChunks()));
+ List standardRedeemScriptChunks = standardRedeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(standardRedeemScriptChunks));
}
@Test
public void hasNonStandardErpRedeemScriptStructure_whenP2shErpRedeemScript_shouldReturnFalse() {
- Assert.assertFalse(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(p2shErpRedeemScript.getChunks()));
+ List p2shErpRedeemScriptChunks = p2shErpRedeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(p2shErpRedeemScriptChunks));
}
@Test
public void hasNonStandardErpRedeemScriptStructure_whenFlyoverStandardRedeemScript_shouldReturnFalse() {
- Assert.assertFalse(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(flyoverStandardRedeemScript.getChunks()));
+ List flyoverStandardRedeemScriptChunks = flyoverStandardRedeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(flyoverStandardRedeemScriptChunks));
}
@Test
@@ -196,18 +244,14 @@ public void hasNonStandardErpRedeemScriptStructure_whenNonStandardErpFedRedeemSc
10L
);
- Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(redeemScript.getChunks()));
+ List nonStandardErpRedeemsScriptChunks = redeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(nonStandardErpRedeemsScriptChunks));
}
@Test
public void hasNonStandardErpRedeemScriptStructure_whenNonStandardErpFedRedeemScriptTwoBytesCsvValue_shouldReturnTrue() {
- Script redeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys,
- CSV_VALUE
- );
-
- Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(redeemScript.getChunks()));
+ List nonStandardErpRedeemScriptChunks = nonStandardErpRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(nonStandardErpRedeemScriptChunks));
}
@Test
@@ -218,7 +262,8 @@ public void hasNonStandardErpRedeemScriptStructure_whenNonStandardErpFedRedeemSc
130L // Any value above 127 needs an extra byte to indicate the sign
);
- Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(redeemScript.getChunks()));
+ List nonStandardErpRedeemScriptChunks = redeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(nonStandardErpRedeemScriptChunks));
}
@Test
@@ -229,7 +274,8 @@ public void hasNonStandardErpRedeemScriptStructure_whenNonStandardErpFedRedeemSc
100_000L
);
- Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(redeemScript.getChunks()));
+ List nonStandardErpRedeemScriptChunks = redeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(nonStandardErpRedeemScriptChunks));
}
@Test
@@ -240,7 +286,8 @@ public void hasNonStandardErpRedeemScriptStructure_whenNonStandardErpFedRedeemSc
33_000L // Any value above 32_767 needs an extra byte to indicate the sign
);
- Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(redeemScript.getChunks()));
+ List nonStandardErpRedeemScriptChunks = redeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(nonStandardErpRedeemScriptChunks));
}
@Test
@@ -251,7 +298,8 @@ public void hasNonStandardErpRedeemScriptStructure_whenNonStandardErpFedRedeemSc
10_000_000L
);
- Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(redeemScript.getChunks()));
+ List nonStandardErpRedeemScriptChunks = redeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(nonStandardErpRedeemScriptChunks));
}
@Test
@@ -262,25 +310,15 @@ public void hasNonStandardErpRedeemScriptStructure_whenNonStandardErpFedRedeemSc
8_400_000L // Any value above 8_388_607 needs an extra byte to indicate the sign
);
- Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(redeemScript.getChunks()));
+ List nonStandardErpRedeemScriptChunks = redeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(nonStandardErpRedeemScriptChunks));
}
@Test
public void hasNonStandardErpRedeemScriptStructure_whenFlyoverNonStandardErpRedeemScriptRemovingPrefix_shouldReturnTrue() {
- Script redeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys,
- CSV_VALUE
- );
-
- Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- redeemScript
- );
-
// Remove fast bridge prefix
- List chunks = flyoverRedeemScript.getChunks();
- List chunksWithoutFlyoverPrefix = chunks.subList(2, chunks.size());
+ List flyoverNonStandardErpRedeemScriptChunks = flyoverNonStandardErpRedeemScript.getChunks();
+ List chunksWithoutFlyoverPrefix = flyoverNonStandardErpRedeemScriptChunks.subList(2, flyoverNonStandardErpRedeemScriptChunks.size());
Assert.assertTrue(RedeemScriptValidator.hasNonStandardErpRedeemScriptStructure(
chunksWithoutFlyoverPrefix)
@@ -289,69 +327,43 @@ public void hasNonStandardErpRedeemScriptStructure_whenFlyoverNonStandardErpRede
@Test
public void hasFlyoverPrefix_whenEmptyFlyoverPrefix_shouldReturnFalse() {
- Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys);
-
byte[] emptyFlyoverPrefix = {};
Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
emptyFlyoverPrefix,
- redeemScript
+ standardRedeemScript
);
- Assert.assertFalse(RedeemScriptValidator.hasFlyoverPrefix(flyoverRedeemScript.getChunks()));
+ List flyoverRedeemScriptChunks = flyoverRedeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasFlyoverPrefix(flyoverRedeemScriptChunks));
}
@Test
public void hasFlyoverPrefix_whenStandardMultisigRedeemScript_shouldReturnFalse() {
- Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys);
- Assert.assertFalse(RedeemScriptValidator.hasFlyoverPrefix(redeemScript.getChunks()));
+ List standardRedeemScriptChunks = standardRedeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasFlyoverPrefix(standardRedeemScriptChunks));
}
@Test
public void hasFlyoverPrefix_whenP2shErpRedeemScript_shouldReturnFalse() {
- Script redeemScript = RedeemScriptUtils.createP2shErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys,
- CSV_VALUE
- );
- Assert.assertFalse(RedeemScriptValidator.hasFlyoverPrefix(redeemScript.getChunks()));
+ List p2shErpRedeemScriptChunks = p2shErpRedeemScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasFlyoverPrefix(p2shErpRedeemScriptChunks));
}
@Test
public void hasFlyoverPrefix_whenScriptSig_shouldReturnFalse() {
- Script redeemScript = RedeemScriptUtils.createP2shErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys,
- CSV_VALUE
- );
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverP2shErpRedeemScript);
+ Script scriptSig = p2shOutputScript.createEmptyInputScript(null, flyoverP2shErpRedeemScript);
- Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- redeemScript
- );
-
- Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript);
-
- Script scriptSig = p2shOutputScript.createEmptyInputScript(null, flyoverRedeemScript);
-
- Assert.assertFalse(RedeemScriptValidator.hasFlyoverPrefix(scriptSig.getChunks()));
+ List scriptSigChunks = scriptSig.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasFlyoverPrefix(scriptSigChunks));
}
@Test
public void hasFlyoverPrefix_whenP2shOutputScript_shouldReturnFalse() {
- Script redeemScript = RedeemScriptUtils.createP2shErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys,
- CSV_VALUE
- );
-
- Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- redeemScript
- );
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverP2shErpRedeemScript);
- Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript);
-
- Assert.assertFalse(RedeemScriptValidator.hasFlyoverPrefix(p2shOutputScript.getChunks()));
+ List p2shOutputScriptChunks = p2shOutputScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasFlyoverPrefix(p2shOutputScriptChunks));
}
@Test
@@ -363,7 +375,8 @@ public void hasFlyoverPrefix_whenFlyoverPrefixAndInvalidScript_shouldReturnTrue(
invalidScript
);
- Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverRedeemScript.getChunks()));
+ List flyoverRedeemScriptChunks = flyoverRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverRedeemScriptChunks));
}
@Test
@@ -375,62 +388,37 @@ public void hasFlyoverPrefix_whenZeroFlyoverPrefixAndInvalidScript_shouldReturnT
invalidScript
);
- Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverRedeemScript.getChunks()));
+ List flyoverRedeemScriptChunks = flyoverRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverRedeemScriptChunks));
}
@Test
public void hasFlyoverPrefix_whenFlyoverNonStandardErpRedeemScriptParserHardcoded_shouldReturnTrue() {
- Script redeemScript = new Script(NON_STANDARD_ERP_TESTNET_REDEEM_SCRIPT_SERIALIZED);
-
- Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
+ Script flyoverNonStandardErpTestnetRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
FLYOVER_DERIVATION_HASH.getBytes(),
- redeemScript
+ nonStandardErpTestnetRedeemScript
);
- Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverRedeemScript.getChunks()));
+ List flyoverNonStandardErpTestnetRedeemScriptChunks = flyoverNonStandardErpTestnetRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverNonStandardErpTestnetRedeemScriptChunks));
}
@Test
public void hasFlyoverPrefix_whenFlyoverStandardMultisigRedeemScript_shouldReturnTrue() {
- Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys);
- Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- redeemScript
- );
-
- Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverRedeemScript.getChunks()));
+ List flyoverStandardRedeemScriptChunks = flyoverStandardRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverStandardRedeemScriptChunks));
}
@Test
public void hasFlyoverPrefix_whenFlyoverNonStandardErpRedeemScript_shouldReturnTrue() {
- Script nonStandardErpRedeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys,
- CSV_VALUE
- );
-
- Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- nonStandardErpRedeemScript
- );
-
- Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverRedeemScript.getChunks()));
+ List flyoverNonStandardErpRedeemScriptChunks = flyoverNonStandardErpRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverNonStandardErpRedeemScriptChunks));
}
@Test
public void hasFlyoverPrefix_whenFlyoverP2shErpRedeemScript_shouldReturnTrue() {
- Script redeemScript = RedeemScriptUtils.createP2shErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys,
- CSV_VALUE
- );
-
- Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- redeemScript
- );
-
- Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverRedeemScript.getChunks()));
+ List flyoverP2shErpRedeemScriptChunks = flyoverP2shErpRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasFlyoverPrefix(flyoverP2shErpRedeemScriptChunks));
}
@Test
@@ -446,125 +434,82 @@ public void hasFlyoverRedeemScriptStructure_whenFlyoverNoRedeemScript_shouldRetu
@Test
public void hasFlyoverRedeemScriptStructure_whenEmptyFlyoverDerivationHash_shouldReturnFalse() {
- Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys);
List flyoverRedeemScriptChunks = RedeemScriptUtils.createFlyoverRedeemScript(
new byte[]{},
- redeemScript
+ standardRedeemScript
).getChunks();
Assert.assertFalse(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(flyoverRedeemScriptChunks));
}
@Test
public void hasFlyoverRedeemScriptStructure_whenScriptSig_shouldReturnFalse() {
- Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys);
- Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- redeemScript
- );
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverStandardRedeemScript);
+ Script scriptSig = p2shOutputScript.createEmptyInputScript(null, flyoverStandardRedeemScript);
- Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript);
- Script scriptSig = p2shOutputScript.createEmptyInputScript(null, flyoverRedeemScript);
-
- Assert.assertFalse(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(scriptSig.getChunks()));
+ List scriptSigChunks = scriptSig.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(scriptSigChunks));
}
@Test
public void hasFlyoverRedeemScriptStructure_whenP2shOutputScript_shouldReturnFalse() {
- Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys);
- Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- redeemScript
- );
-
- Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript);
-
- Assert.assertFalse(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(p2shOutputScript.getChunks()));
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverStandardRedeemScript);
+ List p2shOutputScriptChunks = p2shOutputScript.getChunks();
+ Assert.assertFalse(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(p2shOutputScriptChunks));
}
@Test
public void hasFlyoverRedeemScriptStructure_whenZeroFlyoverDerivationHash_shouldReturnTrue() {
- Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys);
List flyoverRedeemScriptChunks = RedeemScriptUtils.createFlyoverRedeemScript(
Sha256Hash.ZERO_HASH.getBytes(),
- redeemScript
+ standardRedeemScript
).getChunks();
Assert.assertTrue(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(flyoverRedeemScriptChunks));
}
@Test
public void hasFlyoverRedeemScriptStructure_whenFlyoverStandardMultisigRedeemScript_shouldReturnTrue() {
- Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys);
- List flyoverRedeemScriptChunks = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- redeemScript
- ).getChunks();
- Assert.assertTrue(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(flyoverRedeemScriptChunks));
+ List flyoverStandardRedeemScriptChunks = flyoverStandardRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(flyoverStandardRedeemScriptChunks));
}
@Test
public void hasFlyoverRedeemScriptStructure_whenFlyoverNonStandardErpRedeemScript_shouldReturnTrue() {
- Script redeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys, CSV_VALUE);
- List flyoverRedeemScriptChunks = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- redeemScript
- ).getChunks();
- Assert.assertTrue(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(flyoverRedeemScriptChunks));
+ List flyoverNonStandardErpRedeemScriptChunks = flyoverNonStandardErpRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(flyoverNonStandardErpRedeemScriptChunks));
}
@Test
public void hasFlyoverRedeemScriptStructure_whenFlyoverP2shErpRedeemScript_shouldReturnTrue() {
- Script redeemScript = RedeemScriptUtils.createP2shErpRedeemScript(
- defaultRedeemScriptKeys,
- emergencyRedeemScriptKeys, CSV_VALUE);
- List flyoverRedeemScriptChunks = RedeemScriptUtils.createFlyoverRedeemScript(
- FLYOVER_DERIVATION_HASH.getBytes(),
- redeemScript
- ).getChunks();
- Assert.assertTrue(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(flyoverRedeemScriptChunks));
+ List flyoverP2shErpRedeemScriptChunks = flyoverP2shErpRedeemScript.getChunks();
+ Assert.assertTrue(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(flyoverP2shErpRedeemScriptChunks));
}
@Test
public void hasFlyoverRedeemScriptStructure_whenFlyoverNonStandardErpRedeemScriptParserHardcoded_shouldReturnTrue() {
- Script nonStandardErpTestnetRedeemScript = new Script(NON_STANDARD_ERP_TESTNET_REDEEM_SCRIPT_SERIALIZED);
- List flyoverRedeemScriptChunks = RedeemScriptUtils.createFlyoverRedeemScript(
+ List flyoverNonStandardErpTestnetRedeemScriptChunks = RedeemScriptUtils.createFlyoverRedeemScript(
FLYOVER_DERIVATION_HASH.getBytes(),
nonStandardErpTestnetRedeemScript
).getChunks();
- Assert.assertTrue(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(flyoverRedeemScriptChunks));
+ Assert.assertTrue(RedeemScriptValidator.hasFlyoverRedeemScriptStructure(flyoverNonStandardErpTestnetRedeemScriptChunks));
}
@Test(expected = VerificationException.class)
public void removeOpCheckMultiSig_whenNonStandardErpRedeemScript_ok() {
- Script redeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
+ Script nonStandardErpRedeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
defaultRedeemScriptKeys,
emergencyRedeemScriptKeys,
500L
);
- RedeemScriptValidator.removeOpCheckMultisig(redeemScript);
+ RedeemScriptValidator.removeOpCheckMultisig(nonStandardErpRedeemScript);
}
@Test
public void removeOpCheckMultiSig_standard_redeem_script() {
- Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(defaultRedeemScriptKeys);
- List chunks = RedeemScriptValidator.removeOpCheckMultisig(redeemScript);
+ List chunksWithoutOpCheckMultiSig = RedeemScriptValidator.removeOpCheckMultisig(standardRedeemScript);
- Assert.assertEquals(defaultRedeemScriptKeys.size() + 2, chunks.size()); // 1 chunk per key + OP_M + OP_N
- Assert.assertFalse(RedeemScriptValidator.isRedeemLikeScript(chunks));
- }
-
- @Test
- public void isOpN_valid_opcode() {
- ScriptChunk chunk = new ScriptChunk(ScriptOpCodes.OP_2, null);
- Assert.assertTrue(RedeemScriptValidator.isOpN(chunk));
- }
-
- @Test
- public void isOpnN_invalid_opcode() {
- ScriptChunk chunk = new ScriptChunk(ScriptOpCodes.OP_DROP, null);
- Assert.assertFalse(RedeemScriptValidator.isOpN(chunk));
+ Assert.assertEquals(defaultRedeemScriptKeys.size() + 2, chunksWithoutOpCheckMultiSig.size()); // 1 chunk per key + OP_M + OP_N
+ Assert.assertFalse(RedeemScriptValidator.isRedeemLikeScript(chunksWithoutOpCheckMultiSig));
}
@Test
@@ -580,9 +525,9 @@ public void hasP2shErpRedeemScriptStructure_whenScriptSig_shouldReturnFalse() {
p2shErpRedeemScript
);
Script scriptSig = p2SHOutputScript.createEmptyInputScript(null, p2shErpRedeemScript);
+ List scriptSigChunks = scriptSig.getChunks();
- Assert.assertFalse(RedeemScriptValidator.hasP2shErpRedeemScriptStructure(
- scriptSig.getChunks()));
+ Assert.assertFalse(RedeemScriptValidator.hasP2shErpRedeemScriptStructure(scriptSigChunks));
}
@Test
@@ -590,38 +535,43 @@ public void hasP2shErpRedeemScriptStructure_whenP2shOutputScript_shouldReturnFal
Script p2SHOutputScript = ScriptBuilder.createP2SHOutputScript(
p2shErpRedeemScript
);
+ List p2shOutputScriptChunks = p2SHOutputScript.getChunks();
- Assert.assertFalse(RedeemScriptValidator.hasP2shErpRedeemScriptStructure(
- p2SHOutputScript.getChunks()));
+ Assert.assertFalse(RedeemScriptValidator.hasP2shErpRedeemScriptStructure(p2shOutputScriptChunks));
}
@Test
public void hasP2shErpRedeemScriptStructure_whenStandardRedeemScript_shouldReturnFalse() {
+ List standardRedeemScriptChunks = standardRedeemScript.getChunks();
Assert.assertFalse(RedeemScriptValidator.hasP2shErpRedeemScriptStructure(
- standardRedeemScript.getChunks()));
+ standardRedeemScriptChunks));
}
@Test
public void hasP2shErpRedeemScriptStructure_whenNonStandardErpRedeemScript_shouldReturnFalse() {
+ List nonStandardErpRedeemScriptChunks = nonStandardErpRedeemScript.getChunks();
Assert.assertFalse(RedeemScriptValidator.hasP2shErpRedeemScriptStructure(
- nonstandardErpRedeemScript.getChunks()));
+ nonStandardErpRedeemScriptChunks));
}
@Test
public void hasP2shErpRedeemScriptStructure_whenNonStandardErpRedeemScriptParserHardcoded_shouldReturnFalse() {
+ List nonStandardErpTestnetRedeemScriptChunks = nonStandardErpTestnetRedeemScript.getChunks();
Assert.assertFalse(RedeemScriptValidator.hasP2shErpRedeemScriptStructure(
- nonStandardErpTestnetRedeemScript.getChunks()));
+ nonStandardErpTestnetRedeemScriptChunks));
}
@Test
public void hasP2shErpRedeemScriptStructure_whenFlyoverP2shRedeemScript_shouldReturnFalse() {
+ List flyoverP2shErpRedeemScriptChunks = flyoverP2shErpRedeemScript.getChunks();
Assert.assertFalse(RedeemScriptValidator.hasP2shErpRedeemScriptStructure(
- flyoverP2shErpRedeemScript.getChunks()));
+ flyoverP2shErpRedeemScriptChunks));
}
@Test
public void hasP2shErpRedeemScriptStructure_whenP2shRedeemScript_shouldReturnTrue() {
+ List p2shErpRedeemScriptChunks = p2shErpRedeemScript.getChunks();
Assert.assertTrue(RedeemScriptValidator.hasP2shErpRedeemScriptStructure(
- p2shErpRedeemScript.getChunks()));
+ p2shErpRedeemScriptChunks));
}
}
diff --git a/src/test/java/co/rsk/bitcoinj/script/ScriptBuilderTest.java b/src/test/java/co/rsk/bitcoinj/script/ScriptBuilderTest.java
new file mode 100644
index 000000000..7bf6d2794
--- /dev/null
+++ b/src/test/java/co/rsk/bitcoinj/script/ScriptBuilderTest.java
@@ -0,0 +1,126 @@
+package co.rsk.bitcoinj.script;
+
+import co.rsk.bitcoinj.core.BtcECKey;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.*;
+
+public class ScriptBuilderTest {
+ @Test
+ public void createMultiSigOutputScript_withTwentyPubKeys_shouldReturnAValidScript() {
+ // Arrange
+ int numberOfKeys = 20;
+
+ // Act & Assert
+ assertGivenNumberOfKeysCreatesAValidMultiSigOutputScript(numberOfKeys);
+ }
+
+ @Test
+ public void createMultiSigOutputScript_withFifteenPubKeys_shouldReturnAValidScript() {
+ // Arrange
+ int numberOfKeys = 15;
+
+ // Act & Assert
+ assertGivenNumberOfKeysCreatesAValidMultiSigOutputScript(numberOfKeys);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void createMultiSigOutputScript_withZeroPubKeys_shouldThrowAnException() {
+ // Arrange
+ ArrayList emptyKeys = new ArrayList<>();
+ int expectedThreshold = 1;
+
+ // Act & Assert
+ ScriptBuilder.createMultiSigOutputScript(expectedThreshold, emptyKeys);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void createMultiSigOutputScript_withLessKeysThanTheThreshold_shouldThrowAnException() {
+ // Arrange
+ List ecKeys = RedeemScriptUtils.getNKeys(1);
+ int expectedThreshold = 2;
+
+ // Act & Assert
+ ScriptBuilder.createMultiSigOutputScript(expectedThreshold, ecKeys);
+ }
+
+ @Test
+ public void createMultiSigOutputScript_withOnePubKey_shouldReturnAValidScript() {
+ // Arrange
+ int numberOfKeys = 1;
+
+ // Act & Assert
+ assertGivenNumberOfKeysCreatesAValidMultiSigOutputScript(numberOfKeys);
+ }
+
+ @Test
+ public void createMultiSigOutputScript_withTenPubKeys_shouldReturnAValidScript() {
+ // Arrange
+ int numberOfKeys = 10;
+
+ // Act & Assert
+ assertGivenNumberOfKeysCreatesAValidMultiSigOutputScript(numberOfKeys);
+ }
+
+ @Test
+ public void createMultiSigOutputScript_withSixteenPubKeys_shouldReturnAValidScript() {
+ // Arrange
+ int numberOfKeys = 16;
+
+ // Act & Assert
+ assertGivenNumberOfKeysCreatesAValidMultiSigOutputScript(numberOfKeys);
+ }
+
+ @Test
+ public void createMultiSigOutputScript_with20PubKeys_shouldReturnAValidScript() {
+ // Arrange
+ int numberOfKeys = 20;
+
+ // Act & Assert
+ assertGivenNumberOfKeysCreatesAValidMultiSigOutputScript(numberOfKeys);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void createMultiSigOutputScript_withMoreThan20PubKeys_shouldThrowAnException() {
+ // Arrange
+ List ecKeys = RedeemScriptUtils.getNKeys(21);
+ int expectedThreshold = 11;
+
+ // Act & Assert
+ ScriptBuilder.createMultiSigOutputScript(expectedThreshold, ecKeys);
+ }
+
+ private static void assertGivenNumberOfKeysCreatesAValidMultiSigOutputScript(int numberOfKeys) {
+ List ecKeys = RedeemScriptUtils.getNKeys(numberOfKeys);
+ int expectedThreshold = numberOfKeys / 2 + 1;
+
+ Script multiSigOutputScript = ScriptBuilder.createMultiSigOutputScript(expectedThreshold, ecKeys);
+ assertTrue(multiSigOutputScript.isSentToMultiSig());
+
+ // threshold (1) + pubkeys (numberOfKeys) + num of pubKeys (1) + OP_CHECKMULTISIG (1)
+ int expectedNumberOfChunks = numberOfKeys + 3;
+ List chunks = multiSigOutputScript.getChunks();
+ assertEquals(expectedNumberOfChunks, chunks.size());
+
+ int index = 0;
+ int actualThreshold = chunks.get(index++).decodePositiveN();
+ assertEquals(expectedThreshold, actualThreshold);
+
+ List pubKeys = ecKeys.stream().map(BtcECKey::getPubKey).collect(Collectors.toList());
+ for (int i = 0; i < pubKeys.size(); i++) {
+ byte[] actualPubKeyInTheScript = chunks.get(i + 1).data;
+ assertArrayEquals(pubKeys.get(i), actualPubKeyInTheScript);
+ index++;
+ }
+
+ int actualTotalKeysNumber = chunks.get(index++).decodePositiveN();
+ assertEquals(pubKeys.size(), actualTotalKeysNumber);
+
+ int actualMultiSigOpCode = chunks.get(index).opcode;
+ assertEquals(ScriptOpCodes.OP_CHECKMULTISIG, actualMultiSigOpCode);
+ }
+}
diff --git a/src/test/java/co/rsk/bitcoinj/script/ScriptChunkTest.java b/src/test/java/co/rsk/bitcoinj/script/ScriptChunkTest.java
index 0fe32528e..b0528dc5e 100644
--- a/src/test/java/co/rsk/bitcoinj/script/ScriptChunkTest.java
+++ b/src/test/java/co/rsk/bitcoinj/script/ScriptChunkTest.java
@@ -19,7 +19,9 @@
import static co.rsk.bitcoinj.script.ScriptOpCodes.OP_PUSHDATA1;
import static co.rsk.bitcoinj.script.ScriptOpCodes.OP_PUSHDATA2;
import static co.rsk.bitcoinj.script.ScriptOpCodes.OP_PUSHDATA4;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
@@ -46,4 +48,190 @@ public void testShortestPossibleDataPush() {
assertFalse("push of 255 bytes", new ScriptChunk(OP_PUSHDATA2, new byte[255]).isShortestPossiblePushData());
assertFalse("push of 65535 bytes", new ScriptChunk(OP_PUSHDATA4, new byte[65535]).isShortestPossiblePushData());
}
+
+ @Test
+ public void isOpCheckMultiSig_withNullData_returnsTrue() {
+ ScriptChunk chunk = new ScriptChunk(ScriptOpCodes.OP_CHECKMULTISIG, null);
+ assertTrue(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_CHECKMULTISIGVERIFY, null);
+ assertTrue(chunk.isOpCheckMultiSig());
+ }
+
+ @Test
+ public void isOpCheckMultiSig_withEmptyData_returnsTrue() {
+ byte[] emptyData = new byte[]{};
+
+ ScriptChunk chunk = new ScriptChunk(ScriptOpCodes.OP_CHECKMULTISIG, emptyData);
+ assertTrue(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_CHECKMULTISIGVERIFY, emptyData);
+ assertTrue(chunk.isOpCheckMultiSig());
+ }
+
+ @Test
+ public void isOpCheckMultiSig_withData_returnsTrue() {
+ byte[] randomData = new byte[]{1, 2, 3};
+
+ ScriptChunk chunk = new ScriptChunk(ScriptOpCodes.OP_CHECKMULTISIG, randomData);
+ assertTrue(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_CHECKMULTISIGVERIFY, randomData);
+ assertTrue(chunk.isOpCheckMultiSig());
+ }
+
+ @Test
+ public void isOpCheckMultiSig_withNonOpCheckMultiSig_nullData_returnsFalse() {
+ ScriptChunk chunk = new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, null);
+ assertFalse(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_RETURN, null);
+ assertFalse(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_0, null);
+ assertFalse(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_1, null);
+ assertFalse(chunk.isOpCheckMultiSig());
+ }
+
+ @Test
+ public void isOpCheckMultiSig_withNonOpCheckMultiSig_emptyData_returnsFalse() {
+ byte[] emptyData = new byte[]{};
+
+ ScriptChunk chunk = new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, emptyData);
+ assertFalse(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_RETURN, emptyData);
+ assertFalse(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_0, emptyData);
+ assertFalse(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_1, emptyData);
+ assertFalse(chunk.isOpCheckMultiSig());
+ }
+
+ @Test
+ public void isOpCheckMultiSig_withNonOpCheckMultiSig_withData_returnsFalse() {
+ byte[] randomData = new byte[]{1, 2, 3};
+
+ ScriptChunk chunk = new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, randomData);
+ assertFalse(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_RETURN, randomData);
+ assertFalse(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_0, randomData);
+ assertFalse(chunk.isOpCheckMultiSig());
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_1, randomData);
+ assertFalse(chunk.isOpCheckMultiSig());
+ }
+
+ @Test
+ public void isOpCheckMultiSig_withNonOpCode_returnsFalse() {
+ ScriptChunk chunk = new ScriptChunk(OP_PUSHDATA1, null);
+ assertFalse(chunk.isOpCode());
+ assertFalse(chunk.isOpCheckMultiSig());
+ }
+
+ @Test
+ public void decodePositiveN_withPositiveSmallNumber_returnsExpectedNumber() {
+ for (int i=1; i<16; i++) {
+ ScriptBuilder builder = new ScriptBuilder();
+ builder.number(i);
+ Script script = builder.build();
+
+ ScriptChunk chunk = script.chunks.get(0);
+ assertTrue(chunk.isPositiveN());
+ assertEquals(i, chunk.decodePositiveN());
+ }
+ }
+
+ @Test
+ public void decodePositiveN_withPositiveNumber_returnsExpectedNumber() {
+ for (int n=0; n<20; n++) { // technically we could go up to Integer.MAX_VALUE, but it's not worth it to go that far
+ int i = 1 << n; // i = 2^n
+ ScriptBuilder builder = new ScriptBuilder();
+ builder.number(i);
+ Script script = builder.build();
+
+ ScriptChunk chunk = script.chunks.get(0);
+ assertTrue(chunk.isPositiveN());
+ assertEquals(i, chunk.decodePositiveN());
+ }
+ }
+
+ @Test
+ public void decodePositiveN_forZero_throwsIAE() {
+ ScriptBuilder builder = new ScriptBuilder();
+ int zero = 0;
+ builder.number(zero);
+ Script script = builder.build();
+
+ ScriptChunk chunk = script.chunks.get(0);
+ assertFalse(chunk.isPositiveN());
+ assertThrows(IllegalArgumentException.class, chunk::decodePositiveN);
+ }
+
+ @Test
+ public void decodePositiveN_withNegativeNumber_throwsIAE() {
+ for (int n=0; n<20; n++) { // technically we could go down to Integer.MIN_VALUE, but it's not worth it to go that deep
+ int i = -(1 << n); // i = -(2^n)
+ ScriptBuilder builder = new ScriptBuilder();
+ builder.number(i);
+ Script script = builder.build();
+
+ ScriptChunk chunk = script.chunks.get(0);
+ assertFalse(chunk.isPositiveN());
+ assertThrows(IllegalArgumentException.class, chunk::decodePositiveN);
+ }
+ }
+
+ @Test
+ public void decodePositiveN_withNumberLargerThan4Bytes_throwsIAE() {
+ long i = 1L << 32;
+ ScriptBuilder builder = new ScriptBuilder();
+ builder.number(i);
+ Script script = builder.build();
+
+ ScriptChunk chunk = script.chunks.get(0);
+ assertFalse(chunk.isPositiveN());
+ assertThrows(IllegalArgumentException.class, chunk::decodePositiveN);
+ }
+
+ @Test
+ public void decodePositiveN_withNullPushData_throwsIAE() {
+ ScriptChunk chunk = new ScriptChunk(OP_PUSHDATA1, null);
+ assertFalse(chunk.isPositiveN());
+ assertThrows(IllegalArgumentException.class, chunk::decodePositiveN);
+
+ chunk = new ScriptChunk(OP_PUSHDATA2, null);
+ assertFalse(chunk.isPositiveN());
+ assertThrows(IllegalArgumentException.class, chunk::decodePositiveN);
+
+ chunk = new ScriptChunk(OP_PUSHDATA4, null);
+ assertFalse(chunk.isPositiveN());
+ assertThrows(IllegalArgumentException.class, chunk::decodePositiveN);
+ }
+
+ @Test
+ public void decodePositiveN_withWrongOpcodes_throwsIAE() {
+ ScriptChunk chunk = new ScriptChunk(ScriptOpCodes.OP_CHECKMULTISIG, null);
+ assertFalse(chunk.isPositiveN());
+ assertThrows(IllegalArgumentException.class, chunk::decodePositiveN);
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, null);
+ assertFalse(chunk.isPositiveN());
+ assertThrows(IllegalArgumentException.class, chunk::decodePositiveN);
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_RETURN, null);
+ assertFalse(chunk.isPositiveN());
+ assertThrows(IllegalArgumentException.class, chunk::decodePositiveN);
+
+ chunk = new ScriptChunk(ScriptOpCodes.OP_DROP, null);
+ assertFalse(chunk.isPositiveN());
+ assertThrows(IllegalArgumentException.class, chunk::decodePositiveN);
+ }
}
diff --git a/src/test/java/co/rsk/bitcoinj/script/ScriptTest.java b/src/test/java/co/rsk/bitcoinj/script/ScriptTest.java
index 223604aea..ba5edd5a9 100644
--- a/src/test/java/co/rsk/bitcoinj/script/ScriptTest.java
+++ b/src/test/java/co/rsk/bitcoinj/script/ScriptTest.java
@@ -23,21 +23,8 @@
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.*;
-import co.rsk.bitcoinj.core.Address;
-import co.rsk.bitcoinj.core.BtcECKey;
-import co.rsk.bitcoinj.core.BtcTransaction;
+import co.rsk.bitcoinj.core.*;
import co.rsk.bitcoinj.core.BtcTransaction.SigHash;
-import co.rsk.bitcoinj.core.Coin;
-import co.rsk.bitcoinj.core.MessageSerializer;
-import co.rsk.bitcoinj.core.NetworkParameters;
-import co.rsk.bitcoinj.core.ScriptException;
-import co.rsk.bitcoinj.core.Sha256Hash;
-import co.rsk.bitcoinj.core.TransactionInput;
-import co.rsk.bitcoinj.core.TransactionOutPoint;
-import co.rsk.bitcoinj.core.TransactionOutput;
-import co.rsk.bitcoinj.core.UnsafeByteArrayOutputStream;
-import co.rsk.bitcoinj.core.Utils;
-import co.rsk.bitcoinj.core.VerificationException;
import co.rsk.bitcoinj.crypto.TransactionSignature;
import co.rsk.bitcoinj.params.MainNetParams;
import co.rsk.bitcoinj.script.Script.ScriptType;
@@ -52,8 +39,6 @@
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.*;
-import java.util.stream.Collectors;
-
import org.hamcrest.core.IsNot;
import org.junit.Assert;
import org.junit.Test;
@@ -809,6 +794,261 @@ public void isSentToMultiSig_whenNonStandardErpRedeemScript_shouldReturnTrue() {
assertScriptIsMultiSig(nonStandardErpRedeemScript);
}
+ @Test
+ public void getSigsPrefixCount_whenP2PKOutputScript_shouldReturnZero() {
+ BtcECKey signer = FEDERATION_KEYS.get(0);
+ Script p2pkScript = ScriptBuilder.createOutputScript(signer);
+ assertEquals(0, p2pkScript.getSigsPrefixCount());
+ }
+
+ @Test
+ public void getSigsSuffixCount_whenP2PKOutputScript_shouldReturnZero() {
+ BtcECKey signer = FEDERATION_KEYS.get(0);
+ Script p2pkScript = ScriptBuilder.createOutputScript(signer);
+ assertEquals(0, p2pkScript.getSigsSuffixCount());
+ }
+
+ @Test
+ public void getSigsPrefixCount_whenMultiSigOutputScript_shouldReturnOne() {
+ BtcECKey signer = FEDERATION_KEYS.get(0);
+ BtcECKey signer2 = FEDERATION_KEYS.get(1);
+ Script multisigOutputScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(signer, signer2));
+ assertEquals(1, multisigOutputScript.getSigsPrefixCount());
+ }
+
+ @Test
+ public void getSigsSuffixCount_whenMultiSigOutputScript_shouldReturnZero() {
+ BtcECKey signer = FEDERATION_KEYS.get(0);
+ BtcECKey signer2 = FEDERATION_KEYS.get(1);
+ Script multisigOutputScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(signer, signer2));
+ assertEquals(0, multisigOutputScript.getSigsSuffixCount());
+ }
+
+ @Test
+ public void getSigsPrefixCount_whenP2shMultisigOutputScript_shouldReturnOne() {
+ BtcECKey signer = FEDERATION_KEYS.get(0);
+ BtcECKey signer2 = FEDERATION_KEYS.get(1);
+ Script p2shMultisigOutputScript = ScriptBuilder.createP2SHOutputScript(2, Arrays.asList(signer, signer2));
+ assertEquals(1, p2shMultisigOutputScript.getSigsPrefixCount());
+ }
+
+ @Test
+ public void getSigsSuffixCount_whenP2shMultisigOutputScript_shouldReturnOne() {
+ BtcECKey signer = FEDERATION_KEYS.get(0);
+ BtcECKey signer2 = FEDERATION_KEYS.get(1);
+ Script p2shMultisigOutputScript = ScriptBuilder.createP2SHOutputScript(2, Arrays.asList(signer, signer2));
+ assertEquals(1, p2shMultisigOutputScript.getSigsSuffixCount());
+ }
+
+ @Test
+ public void getSigsPrefixCount_whenP2shErpRedeemScript_withP2SHOutputScript_shouldBothReturnOne() {
+ Script p2shErpRedeemScript = createP2shErpRedeemScript(
+ FEDERATION_KEYS,
+ ERP_FEDERATION_KEYS,
+ CSV_VALUE
+ );
+
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(p2shErpRedeemScript);
+
+ assertEquals(1, p2shErpRedeemScript.getSigsPrefixCount());
+ assertEquals(1, p2shOutputScript.getSigsPrefixCount());
+ }
+
+ @Test
+ public void getSigsSuffixCount_whenP2shErpRedeemScript_withP2SHOutputScript_shouldReturnZeroAndOne() {
+ Script p2shErpRedeemScript = createP2shErpRedeemScript(
+ FEDERATION_KEYS,
+ ERP_FEDERATION_KEYS,
+ CSV_VALUE
+ );
+
+ // Actually it should be 2 (redeemScript + OP_NOTIF)
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(p2shErpRedeemScript);
+
+ assertEquals(0, p2shErpRedeemScript.getSigsSuffixCount());
+ assertEquals(1, p2shOutputScript.getSigsSuffixCount());
+ }
+
+ @Test
+ public void getSigsPrefixCount_whenFlyoverStandardRedeemScript_withP2SHOutputScript_shouldBothReturnOne() {
+ Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(FEDERATION_KEYS);
+
+ Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
+ FLYOVER_DERIVATION_HASH,
+ redeemScript
+ );
+
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript);
+
+ assertEquals(1, flyoverRedeemScript.getSigsPrefixCount());
+ assertEquals(1, p2shOutputScript.getSigsPrefixCount());
+ }
+
+ @Test
+ public void getSigsSuffixCount_whenFlyoverStandardRedeemScript_withP2SHOutputScript_shouldReturnZeroAndOne() {
+ Script redeemScript = RedeemScriptUtils.createStandardRedeemScript(FEDERATION_KEYS);
+
+ Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
+ FLYOVER_DERIVATION_HASH,
+ redeemScript
+ );
+
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript);
+
+ assertEquals(0, flyoverRedeemScript.getSigsSuffixCount());
+ assertEquals(1, p2shOutputScript.getSigsSuffixCount());
+ }
+
+ @Test
+ public void getSigsPrefixCount_whenFlyoverNonStandardErpRedeemScript_withP2SHOutputScript_shouldBothReturnOne() {
+ Script nonStandardErpRedeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
+ FEDERATION_KEYS,
+ ERP_FEDERATION_KEYS,
+ CSV_VALUE
+ );
+
+ Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
+ FLYOVER_DERIVATION_HASH,
+ nonStandardErpRedeemScript
+ );
+
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript);
+
+ assertEquals(1, flyoverRedeemScript.getSigsPrefixCount());
+ assertEquals(1, p2shOutputScript.getSigsPrefixCount());
+ }
+
+ @Test
+ public void getSigsSuffixCount_whenFlyoverNonStandardErpRedeemScript_withP2SHOutputScript_shouldReturnZeroAndOne() {
+ Script nonStandardErpRedeemScript = RedeemScriptUtils.createNonStandardErpRedeemScript(
+ FEDERATION_KEYS,
+ ERP_FEDERATION_KEYS,
+ CSV_VALUE
+ );
+
+ Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
+ FLYOVER_DERIVATION_HASH,
+ nonStandardErpRedeemScript
+ );
+
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript);
+
+ assertEquals(0, flyoverRedeemScript.getSigsSuffixCount());
+ assertEquals(1, p2shOutputScript.getSigsSuffixCount());
+ }
+
+ @Test
+ public void getSigsPrefixCount_whenFlyoverP2shErpRedeemScript_withP2SHOutputScript_shouldBothReturnOne() {
+ Script redeemScript = RedeemScriptUtils.createP2shErpRedeemScript(
+ FEDERATION_KEYS,
+ ERP_FEDERATION_KEYS,
+ CSV_VALUE
+ );
+
+ Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
+ FLYOVER_DERIVATION_HASH,
+ redeemScript
+ );
+
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript);
+
+ assertEquals(1, flyoverRedeemScript.getSigsPrefixCount());
+ assertEquals(1, p2shOutputScript.getSigsPrefixCount());
+ }
+
+ @Test
+ public void getSigsSuffixCount_whenFlyoverP2shErpRedeemScript_withP2SHOutputScript_shouldReturnZeroAndOne() {
+ Script redeemScript = RedeemScriptUtils.createP2shErpRedeemScript(
+ FEDERATION_KEYS,
+ ERP_FEDERATION_KEYS,
+ CSV_VALUE
+ );
+
+ Script flyoverRedeemScript = RedeemScriptUtils.createFlyoverRedeemScript(
+ FLYOVER_DERIVATION_HASH,
+ redeemScript
+ );
+
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(flyoverRedeemScript);
+
+ assertEquals(0, flyoverRedeemScript.getSigsSuffixCount());
+ assertEquals(1, p2shOutputScript.getSigsSuffixCount());
+ }
+
+ @Test
+ public void getSigsPrefixCount_whenStandardRedeemScript_withP2SHOutputScript_shouldBothReturnOne() {
+ Script standardRedeemScript = RedeemScriptUtils.createStandardRedeemScript(FEDERATION_KEYS);
+ Script standardP2SHOutputScript = ScriptBuilder.createP2SHOutputScript(standardRedeemScript);
+
+ assertEquals(1, standardRedeemScript.getSigsPrefixCount());
+ assertEquals(1, standardP2SHOutputScript.getSigsPrefixCount());
+ }
+
+ @Test
+ public void getSigsSuffixCount_whenStandardRedeemScript_withP2SHOutputScript_shouldReturnZeroAndOne() {
+ Script standardRedeemScript = RedeemScriptUtils.createStandardRedeemScript(FEDERATION_KEYS);
+ Script standardP2SHOutputScript = ScriptBuilder.createP2SHOutputScript(standardRedeemScript);
+
+ assertEquals(0, standardRedeemScript.getSigsSuffixCount());
+ assertEquals(1, standardP2SHOutputScript.getSigsSuffixCount());
+ }
+
+ @Test
+ public void getSigsPrefixCount_whenNonStandardRedeemScript_withP2SHOutputScript_shouldBothReturnOne() {
+ Script nonStandardErpRedeemScript = createNonStandardErpRedeemScript(
+ FEDERATION_KEYS,
+ ERP_FEDERATION_KEYS,
+ CSV_VALUE
+ );
+
+ Script standardP2SHOutputScript = ScriptBuilder.createP2SHOutputScript(nonStandardErpRedeemScript);
+
+ assertEquals(1, nonStandardErpRedeemScript.getSigsPrefixCount());
+ assertEquals(1, standardP2SHOutputScript.getSigsPrefixCount());
+ }
+
+ @Test
+ public void getSigsSuffixCount_whenNonStandardRedeemScript_withP2SHOutputScript_shouldReturnZeroAndOne() {
+ Script nonStandardErpRedeemScript = createNonStandardErpRedeemScript(
+ FEDERATION_KEYS,
+ ERP_FEDERATION_KEYS,
+ CSV_VALUE
+ );
+
+ Script standardP2SHOutputScript = ScriptBuilder.createP2SHOutputScript(nonStandardErpRedeemScript);
+
+ assertEquals(0, nonStandardErpRedeemScript.getSigsSuffixCount());
+ assertEquals(1, standardP2SHOutputScript.getSigsSuffixCount());
+ }
+
+ @Test
+ public void getSigsPrefixCount_whenStandardRedeemScript_withP2SHP2WSHOutputScript_shouldReturnOne() {
+ Script standardErpRedeemScript = createP2shErpRedeemScript(
+ FEDERATION_KEYS,
+ ERP_FEDERATION_KEYS,
+ CSV_VALUE
+ );
+
+ Script standardP2SHP2WSHOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(standardErpRedeemScript);
+
+ assertEquals(1, standardErpRedeemScript.getSigsPrefixCount());
+ assertEquals(1, standardP2SHP2WSHOutputScript.getSigsPrefixCount());
+ }
+
+ @Test
+ public void getSigsSuffixCount_whenStandardRedeemScript_withP2SHP2WSHOutputScript_shouldReturnOne() {
+ Script standardErpRedeemScript = createP2shErpRedeemScript(
+ FEDERATION_KEYS,
+ ERP_FEDERATION_KEYS,
+ CSV_VALUE
+ );
+
+ Script standardP2SHP2WSHOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(standardErpRedeemScript);
+
+ assertEquals(0, standardErpRedeemScript.getSigsSuffixCount());
+ assertEquals(1, standardP2SHP2WSHOutputScript.getSigsSuffixCount());
+ }
+
@Test
public void isSentToMultiSig_whenP2shErpRedeemScript_shouldReturnTrue() {
Script p2shErpRedeemScript = createP2shErpRedeemScript(
diff --git a/src/test/java/co/rsk/bitcoinj/wallet/WalletTest.java b/src/test/java/co/rsk/bitcoinj/wallet/WalletTest.java
index 929a890fc..274d58c36 100644
--- a/src/test/java/co/rsk/bitcoinj/wallet/WalletTest.java
+++ b/src/test/java/co/rsk/bitcoinj/wallet/WalletTest.java
@@ -1,7 +1,6 @@
package co.rsk.bitcoinj.wallet;
import co.rsk.bitcoinj.core.*;
-import co.rsk.bitcoinj.params.UnitTestParams;
import co.rsk.bitcoinj.script.Script;
import co.rsk.bitcoinj.script.ScriptBuilder;
import org.junit.Test;
@@ -10,24 +9,13 @@
import java.nio.charset.StandardCharsets;
import java.util.*;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
public class WalletTest {
// data from tx https://mempool.space/testnet/tx/1744459aeaf7369aadc9fc40de9ab2bf575b14e35029b35a7ee4bbd3de65af7f
private static final NetworkParameters TESTNET = NetworkParameters.fromID(NetworkParameters.ID_TESTNET);
private static final Address ADDRESS_TO = Address.fromBase58(TESTNET, "mwUXQVdcCwCeYJx7mBhH4yLBU5N6QDSBZK");
-
- private static final BtcECKey PUBLIC_KEY_1 = BtcECKey.fromPublicOnly(Hex.decode("027de2af71862e0c64bf0ec5a66e3abc3b01fc57877802e6a6a81f6ea1d3561007"));
- private static final BtcECKey PUBLIC_KEY_2 = BtcECKey.fromPublicOnly(Hex.decode("02d9c67fef9f8d0707cbcca195eb5f26c6a65da6ca2d6130645c434bb924063856"));
- private static final BtcECKey PUBLIC_KEY_3 = BtcECKey.fromPublicOnly(Hex.decode("0346f033b8652a17d319d3ecbbbf20fd2cd663a6548173b9419d8228eef095012e"));
- private static final List PUBLIC_KEYS = Arrays.asList(PUBLIC_KEY_1, PUBLIC_KEY_2, PUBLIC_KEY_3);
- private static final Script REDEEM_SCRIPT = new Script(
- Hex.decode("5221027de2af71862e0c64bf0ec5a66e3abc3b01fc57877802e6a6a81f6ea1d35610072102d9c67fef9f8d0707cbcca195eb5f26c6a65da6ca2d6130645c434bb924063856210346f033b8652a17d319d3ecbbbf20fd2cd663a6548173b9419d8228eef095012e53ae")
- );
-
- private static final Sha256Hash UTXO_HASH = Sha256Hash.of("hash".getBytes(StandardCharsets.UTF_8));
- private static final Coin AVAILABLE_AMOUNT = Coin.FIFTY_COINS;
+ private static List PUBLIC_KEYS;
// CoinSelector and UTXOProvider are just interfaces,
// so rskj has its own implementations for them.
@@ -47,6 +35,11 @@ public class WalletTest {
return new CoinSelection(Coin.valueOf(total), selected);
};
+ private static Script REDEEM_SCRIPT;
+ private static int AMOUNT_OF_UTXOS_TO_USE;
+ private static Coin AMOUNT_IN_EACH_UTXO;
+ private static Coin VALUE_TO_SEND;
+
private Wallet wallet;
private BtcTransaction tx;
private SendRequest sr;
@@ -63,29 +56,36 @@ public RedeemData findRedeemDataFromScriptHash(byte[] payToScriptHash) {
};
wallet.setCoinSelector(COIN_SELECTOR);
- UTXOProvider utxoProvider = getUtxoProvider(scriptPubKey);
+ UTXOProvider utxoProvider = getUtxosProvider(scriptPubKey);
wallet.setUTXOProvider(utxoProvider);
Address address = Address.fromP2SHScript(TESTNET, scriptPubKey);
wallet.addWatchedAddress(address);
tx = new BtcTransaction(TESTNET);
- Coin valueToSend = Coin.FIFTY_COINS;
- tx.addOutput(valueToSend, ADDRESS_TO);
+ tx.addOutput(VALUE_TO_SEND, ADDRESS_TO);
sr = SendRequest.forTx(tx);
sr.signInputs = false;
sr.recipientsPayFees = true;
sr.feePerKb = Coin.valueOf(10000L);
+ sr.changeAddress = Address.fromP2SHScript(TESTNET, scriptPubKey);
+ sr.shuffleOutputs = false;
}
- private static UTXOProvider getUtxoProvider(Script scriptPubKey) {
- UTXO utxo = new UTXO(UTXO_HASH, 0, AVAILABLE_AMOUNT, 10, false, scriptPubKey);
+ private static UTXOProvider getUtxosProvider(Script scriptPubKey) {
+ List utxos = new ArrayList<>();
+ for (int i = 0; i < AMOUNT_OF_UTXOS_TO_USE; i++) {
+ String hash = "hash" + "i";
+ Sha256Hash utxoHash = Sha256Hash.of(hash.getBytes(StandardCharsets.UTF_8));
+ UTXO utxo = new UTXO(utxoHash, i, AMOUNT_IN_EACH_UTXO, 10, false, scriptPubKey);
+ utxos.add(utxo);
+ }
return new UTXOProvider() {
@Override
public List getOpenTransactionOutputs(List addresses) {
- return Collections.singletonList(utxo);
+ return utxos;
}
@Override
@@ -101,9 +101,20 @@ public NetworkParameters getParams() {
}
@Test
- public void completeTx_legacy() throws InsufficientMoneyException {
+ public void completeTx_legacyTx_shouldCalculateTxSizeCorrectly() throws InsufficientMoneyException {
// arrange
+ final BtcECKey publicKey1 = BtcECKey.fromPublicOnly(Hex.decode("027de2af71862e0c64bf0ec5a66e3abc3b01fc57877802e6a6a81f6ea1d3561007"));
+ final BtcECKey publicKey2 = BtcECKey.fromPublicOnly(Hex.decode("02d9c67fef9f8d0707cbcca195eb5f26c6a65da6ca2d6130645c434bb924063856"));
+ final BtcECKey publicKey3 = BtcECKey.fromPublicOnly(Hex.decode("0346f033b8652a17d319d3ecbbbf20fd2cd663a6548173b9419d8228eef095012e"));
+ PUBLIC_KEYS = Arrays.asList(publicKey1, publicKey2, publicKey3);
+ REDEEM_SCRIPT = new Script(
+ Hex.decode("5221027de2af71862e0c64bf0ec5a66e3abc3b01fc57877802e6a6a81f6ea1d35610072102d9c67fef9f8d0707cbcca195eb5f26c6a65da6ca2d6130645c434bb924063856210346f033b8652a17d319d3ecbbbf20fd2cd663a6548173b9419d8228eef095012e53ae")
+ );
Script legacyScriptPubKey = ScriptBuilder.createP2SHOutputScript(REDEEM_SCRIPT);
+
+ VALUE_TO_SEND = Coin.FIFTY_COINS;
+ AMOUNT_OF_UTXOS_TO_USE = 1;
+ AMOUNT_IN_EACH_UTXO = Coin.FIFTY_COINS;
setUp(legacyScriptPubKey);
// act
@@ -111,17 +122,29 @@ public void completeTx_legacy() throws InsufficientMoneyException {
wallet.completeTx(sr);
// assert
- double expectedSize = 375;
+ double realSize = 375;
+ int expectedCalculatedSize = 340;
// for legacy tx, the calculation has a 10% diff approx,
// but we have to keep the same implementation for backwards compatibility
double allowedPercentageError = 10.0;
- assertCalculatedSizeIsCloseToExpectedSize(expectedSize, allowedPercentageError);
+ assertCalculatedSizeIsCloseToExpectedSize(realSize, allowedPercentageError, expectedCalculatedSize);
}
@Test
- public void completeTx_segwit() throws InsufficientMoneyException {
+ public void completeTx_segwitTx_shouldCalculateTxSizeCorrectly() throws InsufficientMoneyException {
// arrange
+ final BtcECKey publicKey1 = BtcECKey.fromPublicOnly(Hex.decode("027de2af71862e0c64bf0ec5a66e3abc3b01fc57877802e6a6a81f6ea1d3561007"));
+ final BtcECKey publicKey2 = BtcECKey.fromPublicOnly(Hex.decode("02d9c67fef9f8d0707cbcca195eb5f26c6a65da6ca2d6130645c434bb924063856"));
+ final BtcECKey publicKey3 = BtcECKey.fromPublicOnly(Hex.decode("0346f033b8652a17d319d3ecbbbf20fd2cd663a6548173b9419d8228eef095012e"));
+ PUBLIC_KEYS = Arrays.asList(publicKey1, publicKey2, publicKey3);
+ REDEEM_SCRIPT = new Script(
+ Hex.decode("5221027de2af71862e0c64bf0ec5a66e3abc3b01fc57877802e6a6a81f6ea1d35610072102d9c67fef9f8d0707cbcca195eb5f26c6a65da6ca2d6130645c434bb924063856210346f033b8652a17d319d3ecbbbf20fd2cd663a6548173b9419d8228eef095012e53ae")
+ );
Script scriptPubKey = ScriptBuilder.createP2SHP2WSHOutputScript(REDEEM_SCRIPT);
+
+ VALUE_TO_SEND = Coin.FIFTY_COINS;
+ AMOUNT_OF_UTXOS_TO_USE = 1;
+ AMOUNT_IN_EACH_UTXO = Coin.FIFTY_COINS;
setUp(scriptPubKey);
// act
@@ -129,22 +152,69 @@ public void completeTx_segwit() throws InsufficientMoneyException {
wallet.completeTx(sr);
// assert
- double expectedSize = 183.75;
+ double realSize = 183.75;
+ int expectedCalculatedSize = 184;
// for segwit, the calculation seems to be pretty accurate
double allowedPercentageError = 1.0;
- assertCalculatedSizeIsCloseToExpectedSize(expectedSize, allowedPercentageError);
+ assertCalculatedSizeIsCloseToExpectedSize(realSize, allowedPercentageError, expectedCalculatedSize);
+ }
+
+ @Test
+ public void completeTx_forTxThatExceedsMaximumStandardSize_throwsExceededMaxTransactionSizeException() {
+ // inspired from tx https://mempool.space/testnet/tx/2cadd21b8b5a188afed27754a3f1a97324bd45ac8ec61ce57ff79e90b9fad8a7
+
+ // arrange
+ final BtcECKey publicKey1 = BtcECKey.fromPublicOnly(Hex.decode("0211310637a4062844098b46278059298cc948e1ff314ca9ec75c82e0d0b8ad22c"));
+ final BtcECKey publicKey2 = BtcECKey.fromPublicOnly(Hex.decode("0238de69e208565fd82e4b76c4eff5d817a51679b8a90c41709e49660ba23501c5"));
+ final BtcECKey publicKey3 = BtcECKey.fromPublicOnly(Hex.decode("024b120731b26ec7165cddd214fc8e3f0c844a03dc0e533fb0cf9f89ad2f68a881"));
+ final BtcECKey publicKey4 = BtcECKey.fromPublicOnly(Hex.decode("0274564db76110474ac0d7e09080c182855b22a864cc201ed55217b23301f52f22"));
+ final BtcECKey publicKey5 = BtcECKey.fromPublicOnly(Hex.decode("02867f0e693a2553bf2bc13a5efa0b516b28e66317fbe8e484dd3f375bcb48ec59"));
+ final BtcECKey publicKey6 = BtcECKey.fromPublicOnly(Hex.decode("02881af2910c909f224557353dd28e3729363cf5c24232f26d25c92dac72a3dcdb"));
+ final BtcECKey publicKey7 = BtcECKey.fromPublicOnly(Hex.decode("029c75b3257e0842c4be48e57e39bf2735c74a76c4b1c0b08d1cc66bf5b8748cc1"));
+ final BtcECKey publicKey8 = BtcECKey.fromPublicOnly(Hex.decode("02a46cbe93287cb51a398a157de2b428f21a94f46affdd916ce921bd10db652033"));
+ final BtcECKey publicKey9 = BtcECKey.fromPublicOnly(Hex.decode("02d335ef4eeb74330c3a53f529f9741fa096412c7982ed681fcf69763894f34f89"));
+ final BtcECKey publicKey10 = BtcECKey.fromPublicOnly(Hex.decode("02d3f5fd6e107cf68b1be8dce0e16a0a8afb8dcef9a76c851d7eaf6d51c46a3575"));
+ final BtcECKey publicKey11 = BtcECKey.fromPublicOnly(Hex.decode("03163b86a62b4eeeb52f67cb16ce13a8622a066f2a063280749b956a97705dfc3d"));
+ final BtcECKey publicKey12 = BtcECKey.fromPublicOnly(Hex.decode("033267e382e076cbaa199d49ea7362535f95b135de181caf66b391f541bf39ab0e"));
+ final BtcECKey publicKey13 = BtcECKey.fromPublicOnly(Hex.decode("0343e106d90183e2eef7d5cb7538a634439bf1301d731787c6736922ff19e750ed"));
+ final BtcECKey publicKey14 = BtcECKey.fromPublicOnly(Hex.decode("034461d4263b907cfc5ebb468f19d6a133b567f3cc4855e8725faaf60c6e388bca"));
+ final BtcECKey publicKey15 = BtcECKey.fromPublicOnly(Hex.decode("036e92e6555d2e70af4f5a4f888145356e60bb1a5bc00786a8e9f50152090b2f69"));
+ final BtcECKey publicKey16 = BtcECKey.fromPublicOnly(Hex.decode("03ab54da6b69407dcaaa85f6904687052c93f1f9dd0633f1321b3e624fcd30144b"));
+ final BtcECKey publicKey17 = BtcECKey.fromPublicOnly(Hex.decode("03bd5b51b1c5d799da190285c8078a2712b8e5dc6f73c799751e6256bb89a4bd04"));
+ final BtcECKey publicKey18 = BtcECKey.fromPublicOnly(Hex.decode("03be060191c9632184f2a0ab2638eeed04399372f37fc7a3cff5291cfd6426cf35"));
+ final BtcECKey publicKey19 = BtcECKey.fromPublicOnly(Hex.decode("03e6def9ef0597336eb58d24f955b6b63756cf7b3885322f9d0cf5a2a12f7e459b"));
+ final BtcECKey publicKey20 = BtcECKey.fromPublicOnly(Hex.decode("03ef03253b7b4f33d68c39141eb016df15fafbb1d0fa4a2e7f208c94ea154ab8c3"));
+ PUBLIC_KEYS = Arrays.asList(
+ publicKey1, publicKey2, publicKey3, publicKey4, publicKey5, publicKey6, publicKey7, publicKey8, publicKey9, publicKey10,
+ publicKey11, publicKey12, publicKey13, publicKey14, publicKey15, publicKey16, publicKey17, publicKey18, publicKey19, publicKey20
+ );
+ byte[] rawRedeemScript = Hex.decode("645b210211310637a4062844098b46278059298cc948e1ff314ca9ec75c82e0d0b8ad22c210238de69e208565fd82e4b76c4eff5d817a51679b8a90c41709e49660ba23501c521024b120731b26ec7165cddd214fc8e3f0c844a03dc0e533fb0cf9f89ad2f68a881210274564db76110474ac0d7e09080c182855b22a864cc201ed55217b23301f52f222102867f0e693a2553bf2bc13a5efa0b516b28e66317fbe8e484dd3f375bcb48ec592102881af2910c909f224557353dd28e3729363cf5c24232f26d25c92dac72a3dcdb21029c75b3257e0842c4be48e57e39bf2735c74a76c4b1c0b08d1cc66bf5b8748cc12102a46cbe93287cb51a398a157de2b428f21a94f46affdd916ce921bd10db6520332102d335ef4eeb74330c3a53f529f9741fa096412c7982ed681fcf69763894f34f892102d3f5fd6e107cf68b1be8dce0e16a0a8afb8dcef9a76c851d7eaf6d51c46a35752103163b86a62b4eeeb52f67cb16ce13a8622a066f2a063280749b956a97705dfc3d21033267e382e076cbaa199d49ea7362535f95b135de181caf66b391f541bf39ab0e210343e106d90183e2eef7d5cb7538a634439bf1301d731787c6736922ff19e750ed21034461d4263b907cfc5ebb468f19d6a133b567f3cc4855e8725faaf60c6e388bca21036e92e6555d2e70af4f5a4f888145356e60bb1a5bc00786a8e9f50152090b2f692103ab54da6b69407dcaaa85f6904687052c93f1f9dd0633f1321b3e624fcd30144b2103bd5b51b1c5d799da190285c8078a2712b8e5dc6f73c799751e6256bb89a4bd042103be060191c9632184f2a0ab2638eeed04399372f37fc7a3cff5291cfd6426cf352103e6def9ef0597336eb58d24f955b6b63756cf7b3885322f9d0cf5a2a12f7e459b2103ef03253b7b4f33d68c39141eb016df15fafbb1d0fa4a2e7f208c94ea154ab8c30114ae67011eb2755b21021a560245f78312588f600315d75d493420bed65873b63d0d4bb8ca1b9163a35b2102218e9dc07ac4190a1d7df94fc75953b36671129f12668a94f1f504fe47399ead210272ed6e14e70f6b4757d412729730837bc63b6313276be8308a5a96afd63af9942102872f69892a74d60f6185c2908414dcddb24951c035a1a8466c6c56f55043e7602102886d7d8e865f75dfda3ddf94619af87ad8aa71e8ef393e1e57593576b7d7af1621028e59462fb53ba31186a353b7ea77ebefda9097392e45b7ca7a216168230d05af21028f5a88b08d75765b36951254e68060759de5be7e559972c37c67fc8cedafeb262102c9ced4bbc468af9ace1645df2fd50182d5822cb4c68aae0e50ae1d45da260d2a2102deba35a96add157b6de58f48bb6e23bcb0a17037bed1beb8ba98de6b0a0d71d62102f2e00fefa5868e2c56405e188ec1d97557a7c77fb6a448352cc091c2ae9d50492102fb8c06c723d4e59792e36e6226087fcfac65c1d8a0d5c5726a64102a551528442103077c62a45ea1a679e54c9f7ad800d8e40eaf6012657c8dccd3b61d5e070d9a432103616959a72dd302043e9db2dbd7827944ecb2d555a8f72a48bb8f916ec5aac6ec210362f9c79cd0586704d6a9ea863573f3b123d90a31faaa5a1d9a69bf9631c78ae321036899d94ad9d3f24152dd4fa79b9cb8dddbd26d18297be4facb295f57c9de60bd210376e4cb35baa8c46b0dcffaf303785c5f7aadf457df30ac956234cc8114e2f47d2103a587256beec4e167aebc478e1d6502bb277a596ae9574ccb646da11fffbf36502103bb9da162c3f581ced93167f86d7e0e5962762a1188f5bd1f8b5d08fed46ef73d2103c34fcd05cef2733ea7337c37f50ae26245646aba124948c6ff8dcdf8212849982103f8ac768e683a07ac4063f72a6d856aedeae109f844abcfa34ac9519d715177460114ae68");
+ REDEEM_SCRIPT = new Script(rawRedeemScript);
+ Script scriptPubKey = ScriptBuilder.createP2SHP2WSHOutputScript(REDEEM_SCRIPT);
+
+ AMOUNT_OF_UTXOS_TO_USE = 170;
+ AMOUNT_IN_EACH_UTXO = Coin.valueOf(10_000);
+ VALUE_TO_SEND = AMOUNT_IN_EACH_UTXO.multiply(AMOUNT_OF_UTXOS_TO_USE); // to have to use all the utxos
+
+ setUp(scriptPubKey);
+ sr.isSegwitCompatible = true;
+
+ // act & assert
+ assertThrows(Wallet.ExceededMaxTransactionSize.class, () -> wallet.completeTx(sr));
}
- private void assertCalculatedSizeIsCloseToExpectedSize(double expectedSize, double allowedPercentageError) {
- double allowedError = Math.ceil(expectedSize * allowedPercentageError / 100);
+ private void assertCalculatedSizeIsCloseToExpectedSize(double realSize, double allowedPercentageError, int expectedCalculatedSize) {
+ double allowedError = Math.ceil(realSize * allowedPercentageError / 100);
+ Coin availableAmount = AMOUNT_IN_EACH_UTXO.multiply(AMOUNT_OF_UTXOS_TO_USE);
Coin actualValueSent = tx.getOutputs().get(0).getValue();
- Coin fee = AVAILABLE_AMOUNT.minus(actualValueSent);
+ Coin fee = availableAmount.minus(actualValueSent);
// fee = (tx size * feePerKb) / 1000 => fee = (tx size * 10_000) / 1000
// => fee = tx size * 10 => tx size = fee / 10
long size = fee.divide(10L).getValue();
+ assertEquals(expectedCalculatedSize, size); // to make the test deterministic
- double diff = Math.abs(expectedSize - size);
+ double diff = Math.abs(realSize - size);
assertTrue(diff <= allowedError);
}
}