Skip to content

Commit dd055ac

Browse files
authored
Merge pull request #174 from rsksmart/segwit_compatible_powpeg_integration_part2
Segwit compatible powpeg integration - Part 2
2 parents ed80c2b + d39fbbb commit dd055ac

14 files changed

+1593
-392
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2121
<modelVersion>4.0.0</modelVersion>
2222
<groupId>co.rsk.bitcoinj</groupId>
23-
<version>0.14.4-rsk-18_2-SNAPSHOT</version>
23+
<version>0.14.4-rsk-18</version>
2424
<artifactId>bitcoinj-thin</artifactId>
2525

2626
<name>bitcoinj-thin</name>

src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package co.rsk.bitcoinj.core;
22

33
import co.rsk.bitcoinj.crypto.TransactionSignature;
4+
import co.rsk.bitcoinj.script.RedeemScriptParser;
5+
import co.rsk.bitcoinj.script.RedeemScriptParserFactory;
6+
import co.rsk.bitcoinj.script.Script;
7+
import com.google.common.base.Preconditions;
48

59
import javax.annotation.Nullable;
6-
import java.util.ArrayList;
7-
import java.util.Arrays;
8-
import java.util.List;
9-
import java.util.Objects;
10+
import java.util.*;
11+
import static com.google.common.base.Preconditions.checkState;
1012

1113
public class TransactionWitness {
1214
static TransactionWitness empty = new TransactionWitness(0);
@@ -66,6 +68,105 @@ public byte[] getScriptBytes() {
6668
return pushes.get(pushes.size() - 1);
6769
}
6870

71+
/**
72+
replicated logic from {@link Script#getSigInsertionIndex}
73+
* */
74+
public int getSigInsertionIndex(Sha256Hash sigHash, BtcECKey signingKey) {
75+
int witnessSize = getPushCount();
76+
int redeemScriptIndex = witnessSize - 1;
77+
byte[] redeemScriptData = getPush(redeemScriptIndex);
78+
Script redeemScript = new Script(redeemScriptData);
79+
RedeemScriptParser redeemScriptParser = RedeemScriptParserFactory.get(redeemScript.getChunks());
80+
81+
int sigInsertionIndex = 0;
82+
int keyIndexInRedeem = redeemScriptParser.findKeyInRedeem(signingKey);
83+
84+
byte[] emptyByte = new byte[]{};
85+
// the pushes that should have the signatures
86+
// are between first one (empty byte for checkmultisig bug)
87+
// and second to last one (op_notif + redeem script)
88+
for (int i = 1; i < getPushCount() - 1; i ++) {
89+
byte[] push = getPush(i);
90+
Preconditions.checkNotNull(push);
91+
if (!Arrays.equals(push, emptyByte)) {
92+
if (keyIndexInRedeem < redeemScriptParser.findSigInRedeem(push, sigHash)) {
93+
return sigInsertionIndex;
94+
}
95+
96+
sigInsertionIndex++;
97+
}
98+
}
99+
100+
return sigInsertionIndex;
101+
}
102+
103+
/**
104+
replicated logic from {@link Script#getScriptSigWithSignature}
105+
* */
106+
public TransactionWitness updateWitnessWithSignature(Script outputScript, byte[] signature, int targetIndex) {
107+
int sigsPrefixCount = outputScript.getSigsPrefixCount();
108+
int sigsSuffixCount = outputScript.getSigsSuffixCount();
109+
return updateWitnessWithSignature(signature, targetIndex, sigsPrefixCount, sigsSuffixCount);
110+
}
111+
112+
/**
113+
replicated logic from {@link co.rsk.bitcoinj.script.ScriptBuilder#updateScriptWithSignature}
114+
* */
115+
private TransactionWitness updateWitnessWithSignature(byte[] signature, int targetIndex, int sigsPrefixCount, int sigsSuffixCount) {
116+
int totalPushes = getPushCount();
117+
118+
byte[] emptyByte = new byte[]{};
119+
// since we fill the signatures in order, checking
120+
// the second to last push is enough to know
121+
// if there's space for new signatures
122+
byte[] secondToLastPush = getPush(totalPushes - sigsSuffixCount - 1);
123+
boolean hasMissingSigs = Arrays.equals(secondToLastPush, emptyByte);
124+
Preconditions.checkArgument(hasMissingSigs, "Witness script is already filled with signatures");
125+
126+
List<byte[]> updatedPushes = new ArrayList<>();
127+
// the signatures appear after the prefix
128+
for (int i = 0; i < sigsPrefixCount; i++) {
129+
byte[] push = getPush(i);
130+
updatedPushes.add(push);
131+
}
132+
133+
int index = 0;
134+
boolean inserted = false;
135+
// copy existing sigs
136+
for (int i = sigsPrefixCount; i < totalPushes - sigsSuffixCount; i++) {
137+
if (index == targetIndex) {
138+
inserted = true;
139+
updatedPushes.add(signature);
140+
++index;
141+
}
142+
143+
byte[] push = getPush(i);
144+
if (!Arrays.equals(push, emptyByte)) {
145+
updatedPushes.add(push);
146+
++index;
147+
}
148+
}
149+
150+
// add zeros for missing signatures
151+
while (index < totalPushes - sigsPrefixCount - sigsSuffixCount) {
152+
if (index == targetIndex) {
153+
inserted = true;
154+
updatedPushes.add(signature);
155+
} else {
156+
updatedPushes.add(emptyByte);
157+
}
158+
index++;
159+
}
160+
161+
// copy the suffix
162+
for (int i = totalPushes - sigsSuffixCount; i < totalPushes; i++) {
163+
byte[] push = getPush(i);
164+
updatedPushes.add(push);
165+
}
166+
167+
checkState(inserted);
168+
return TransactionWitness.of(updatedPushes);
169+
}
69170

70171
@Override
71172
public boolean equals(Object otherObject) {

src/main/java/co/rsk/bitcoinj/script/RedeemScriptValidator.java

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@
55

66
public class RedeemScriptValidator {
77

8+
private RedeemScriptValidator() {
9+
// Prevent instantiation
10+
}
11+
812
protected static boolean isRedeemLikeScript(List<ScriptChunk> chunks) {
913
if (chunks.size() < 4) {
1014
return false;
1115
}
1216

1317
ScriptChunk lastChunk = chunks.get(chunks.size() - 1);
1418
// A standard multisig redeem script must end in OP_CHECKMULTISIG[VERIFY]
15-
boolean isStandard = lastChunk.isOpCode() &&
16-
(lastChunk.equalsOpCode(ScriptOpCodes.OP_CHECKMULTISIG) ||
17-
lastChunk.equalsOpCode(ScriptOpCodes.OP_CHECKMULTISIGVERIFY));
19+
boolean isStandard = lastChunk.isOpCheckMultiSig();
1820
if (isStandard) {
1921
return true;
2022
}
@@ -23,9 +25,7 @@ protected static boolean isRedeemLikeScript(List<ScriptChunk> chunks) {
2325
ScriptChunk penultimateChunk = chunks.get(chunks.size() - 2);
2426
return lastChunk.isOpCode() &&
2527
lastChunk.equalsOpCode(ScriptOpCodes.OP_ENDIF) &&
26-
penultimateChunk.isOpCode() &&
27-
(penultimateChunk.equalsOpCode(ScriptOpCodes.OP_CHECKMULTISIG) ||
28-
penultimateChunk.equalsOpCode(ScriptOpCodes.OP_CHECKMULTISIGVERIFY));
28+
penultimateChunk.isOpCheckMultiSig();
2929
}
3030

3131
protected static boolean hasStandardRedeemScriptStructure(List<ScriptChunk> chunks) {
@@ -34,32 +34,38 @@ protected static boolean hasStandardRedeemScriptStructure(List<ScriptChunk> chun
3434
return false;
3535
}
3636

37-
// First chunk must be an OP_N
38-
if (!isOpN(chunks.get(0))) {
37+
// last chunk should be OP_CHECKMULTISIG
38+
int chunksSize = chunks.size();
39+
ScriptChunk lastChunk = chunks.get(chunksSize - 1);
40+
if (!lastChunk.isOpCheckMultiSig()) {
3941
return false;
4042
}
4143

42-
// Second to last chunk must be an OP_N opcode too, and there should be
43-
// that many data chunks (keys).
44-
ScriptChunk secondToLastChunk = chunks.get(chunks.size() - 2);
45-
if (!isOpN(secondToLastChunk)) {
44+
// First chunk must be a number for the threshold
45+
ScriptChunk firstChunk = chunks.get(0);
46+
// Second to last chunk must be a number for the keys
47+
int secondToLastChunkIndex = chunksSize - 2;
48+
ScriptChunk secondToLastChunk = chunks.get(secondToLastChunkIndex);
49+
50+
if (!(firstChunk.isPositiveN() && secondToLastChunk.isPositiveN())) {
4651
return false;
4752
}
4853

49-
int numKeys = Script.decodeFromOpN(secondToLastChunk.opcode);
50-
if (numKeys < 1 || chunks.size() != numKeys + 3) { // numKeys + M + N + OP_CHECKMULTISIG
54+
int numKeys = secondToLastChunk.decodePositiveN();
55+
// and there should be numKeys+3 total chunks (keys + OP_M + OP_N + OP_CHECKMULTISIG)
56+
if (chunksSize != numKeys + 3) {
5157
return false;
5258
}
5359

54-
for (int i = 1; i < chunks.size() - 2; i++) {
60+
for (int i = 1; i < secondToLastChunkIndex; i++) {
5561
if (chunks.get(i).isOpCode()) { // Should be the public keys, not op_codes
5662
return false;
5763
}
5864
}
5965

6066
return true;
6167
} catch (IllegalStateException e) {
62-
return false; // Not an OP_N opcode.
68+
return false; // Not a number
6369
}
6470
}
6571

@@ -68,10 +74,11 @@ protected static boolean hasP2shErpRedeemScriptStructure(List<ScriptChunk> chunk
6874
return false;
6975
}
7076

71-
ScriptChunk firstChunk = chunks.get(0);
77+
int opNotifIndex = 0;
78+
boolean hasErpPrefix = chunks.get(opNotifIndex).equalsOpCode(ScriptOpCodes.OP_NOTIF);
7279

73-
boolean hasErpPrefix = firstChunk.opcode == ScriptOpCodes.OP_NOTIF;
74-
boolean hasEndIfOpcode = chunks.get(chunks.size() - 1).equalsOpCode(ScriptOpCodes.OP_ENDIF);
80+
int lastChunkIndex = chunks.size() - 1;
81+
boolean hasEndIfOpcode = chunks.get(lastChunkIndex).equalsOpCode(ScriptOpCodes.OP_ENDIF);
7582

7683
if (!hasErpPrefix || !hasEndIfOpcode) {
7784
return false;
@@ -101,8 +108,7 @@ protected static boolean hasP2shErpRedeemScriptStructure(List<ScriptChunk> chunk
101108
return false;
102109
}
103110

104-
/***
105-
* Expected structure:
111+
/* The redeem script structure should be as follows:
106112
* OP_NOTIF
107113
* OP_M
108114
* PUBKEYS...N
@@ -119,7 +125,6 @@ protected static boolean hasP2shErpRedeemScriptStructure(List<ScriptChunk> chunk
119125
* OP_CHECKMULTISIG
120126
* OP_ENDIF
121127
*/
122-
123128
// Validate both default and erp federations redeem scripts.
124129
// Extract the default PowPeg and the emergency multisig redeemscript chunks
125130
List<ScriptChunk> defaultFedRedeemScriptChunks = chunks.subList(1, elseOpcodeIndex);
@@ -218,9 +223,4 @@ protected static List<ScriptChunk> removeOpCheckMultisig(Script redeemScript) {
218223
// Remove the last chunk, which has CHECKMULTISIG op code
219224
return redeemScript.getChunks().subList(0, redeemScript.getChunks().size() - 1);
220225
}
221-
222-
protected static boolean isOpN(ScriptChunk chunk) {
223-
return chunk.isOpCode() &&
224-
chunk.opcode >= ScriptOpCodes.OP_1 && chunk.opcode <= ScriptOpCodes.OP_16;
225-
}
226226
}

src/main/java/co/rsk/bitcoinj/script/Script.java

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,7 @@
2222
import static com.google.common.base.Preconditions.checkArgument;
2323
import static com.google.common.base.Preconditions.checkNotNull;
2424

25-
import co.rsk.bitcoinj.core.Address;
26-
import co.rsk.bitcoinj.core.BtcECKey;
27-
import co.rsk.bitcoinj.core.BtcTransaction;
28-
import co.rsk.bitcoinj.core.NetworkParameters;
29-
import co.rsk.bitcoinj.core.ProtocolException;
30-
import co.rsk.bitcoinj.core.ScriptException;
31-
import co.rsk.bitcoinj.core.Sha256Hash;
32-
import co.rsk.bitcoinj.core.UnsafeByteArrayOutputStream;
33-
import co.rsk.bitcoinj.core.Utils;
25+
import co.rsk.bitcoinj.core.*;
3426
import co.rsk.bitcoinj.crypto.TransactionSignature;
3527
import com.google.common.base.Preconditions;
3628
import com.google.common.collect.Lists;
@@ -40,14 +32,7 @@
4032
import java.math.BigInteger;
4133
import java.security.MessageDigest;
4234
import java.security.NoSuchAlgorithmException;
43-
import java.util.ArrayList;
44-
import java.util.Arrays;
45-
import java.util.Collections;
46-
import java.util.EnumSet;
47-
import java.util.Iterator;
48-
import java.util.LinkedList;
49-
import java.util.List;
50-
import java.util.Set;
35+
import java.util.*;
5136
import javax.annotation.Nullable;
5237
import org.slf4j.Logger;
5338
import org.slf4j.LoggerFactory;
@@ -97,6 +82,9 @@ public enum VerifyFlag {
9782

9883
private static final Logger log = LoggerFactory.getLogger(Script.class);
9984
public static final long MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes
85+
86+
/** The maximum size in bytes of a standard witnessScript */
87+
public static final long MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600;
10088
public static final int SIG_SIZE = 75;
10189
/** Max number of sigops allowed in a standard p2sh redeem script */
10290
public static final int MAX_P2SH_SIGOPS = 15;
@@ -459,19 +447,25 @@ private boolean isErpType(Script redeemScript) {
459447
* Returns a copy of the given scriptSig with the signature inserted in the given position.
460448
*/
461449
public Script getScriptSigWithSignature(Script scriptSig, byte[] sigBytes, int index) {
462-
int sigsPrefixCount = 0;
463-
int sigsSuffixCount = 0;
464-
if (isPayToScriptHash()) {
465-
sigsPrefixCount = 1; // OP_0 <sig>* <redeemScript>
466-
sigsSuffixCount = 1;
467-
} else if (isSentToMultiSig()) {
468-
sigsPrefixCount = 1; // OP_0 <sig>*
469-
} else if (isSentToAddress()) {
470-
sigsSuffixCount = 1; // <sig> <pubkey>
471-
}
450+
int sigsPrefixCount = getSigsPrefixCount();
451+
int sigsSuffixCount = getSigsSuffixCount();
472452
return ScriptBuilder.updateScriptWithSignature(scriptSig, sigBytes, index, sigsPrefixCount, sigsSuffixCount);
473453
}
474454

455+
public int getSigsPrefixCount() {
456+
if (isPayToScriptHash() || isSentToMultiSig()) { // OP_0 <sig>* || OP_0 <sig>*
457+
return 1;
458+
}
459+
return 0;
460+
}
461+
462+
public int getSigsSuffixCount() {
463+
if (isPayToScriptHash() || isSentToAddress()) { // <sig>* <redeemScript> || <sig> <pubkey>
464+
return 1;
465+
}
466+
return 0;
467+
}
468+
475469
private RedeemScriptParser getRedeemScriptParser() {
476470
if (redeemScriptParser == null){
477471
redeemScriptParser = RedeemScriptParserFactory.get(chunks);
@@ -554,22 +548,24 @@ private static int getSigOpCount(List<ScriptChunk> chunks, boolean accurate) thr
554548

555549
static int decodeFromOpN(int opcode) {
556550
checkArgument((opcode == OP_0 || opcode == OP_1NEGATE) || (opcode >= OP_1 && opcode <= OP_16), "decodeFromOpN called on non OP_N opcode");
557-
if (opcode == OP_0)
551+
if (opcode == OP_0) {
558552
return 0;
559-
else if (opcode == OP_1NEGATE)
553+
} else if (opcode == OP_1NEGATE) {
560554
return -1;
561-
else
555+
} else {
562556
return opcode + 1 - OP_1;
557+
}
563558
}
564559

565560
static int encodeToOpN(int value) {
566561
checkArgument(value >= -1 && value <= 16, "encodeToOpN called for " + value + " which we cannot encode in an opcode.");
567-
if (value == 0)
562+
if (value == 0) {
568563
return OP_0;
569-
else if (value == -1)
564+
} else if (value == -1) {
570565
return OP_1NEGATE;
571-
else
566+
} else {
572567
return value - 1 + OP_1;
568+
}
573569
}
574570

575571
/**

0 commit comments

Comments
 (0)