Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor parsers integration #73

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,10 @@ public class ErpFederationRedeemScriptParser extends StandardRedeemScriptParser
public static long MAX_CSV_VALUE = 65_535L; // 2^16 - 1, since bitcoin will interpret up to 16 bits as the CSV value

public ErpFederationRedeemScriptParser(
ScriptType scriptType,
List<ScriptChunk> redeemScriptChunks,
List<ScriptChunk> rawChunks
List<ScriptChunk> redeemScriptChunks
) {
super(
scriptType,
extractStandardRedeemScript(redeemScriptChunks).getChunks(),
rawChunks
extractStandardRedeemScript(redeemScriptChunks).getChunks()
);
this.multiSigType = MultiSigType.ERP_FED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,10 @@ public class FastBridgeErpRedeemScriptParser extends StandardRedeemScriptParser
private static final Logger logger = LoggerFactory.getLogger(FastBridgeErpRedeemScriptParser.class);

public FastBridgeErpRedeemScriptParser(
ScriptType scriptType,
List<ScriptChunk> redeemScriptChunks,
List<ScriptChunk> rawChunks
List<ScriptChunk> redeemScriptChunks
) {
super(
scriptType,
extractStandardRedeemScript(redeemScriptChunks).getChunks(),
rawChunks
extractStandardRedeemScript(redeemScriptChunks).getChunks()
);
this.multiSigType = MultiSigType.FAST_BRIDGE_ERP_FED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,10 @@ public class FastBridgeP2shErpRedeemScriptParser extends StandardRedeemScriptPar
private static final Logger logger = LoggerFactory.getLogger(FastBridgeP2shErpRedeemScriptParser.class);

public FastBridgeP2shErpRedeemScriptParser(
ScriptType scriptType,
List<ScriptChunk> redeemScriptChunks,
List<ScriptChunk> rawChunks
List<ScriptChunk> redeemScriptChunks
) {
super(
scriptType,
extractStandardRedeemScript(redeemScriptChunks).getChunks(),
rawChunks
extractStandardRedeemScript(redeemScriptChunks).getChunks()
);
this.multiSigType = MultiSigType.FAST_BRIDGE_P2SH_ERP_FED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,10 @@ public class FastBridgeRedeemScriptParser extends StandardRedeemScriptParser {
protected final byte[] derivationHash;

public FastBridgeRedeemScriptParser(
ScriptType scriptType,
List<ScriptChunk> redeemScriptChunks,
List<ScriptChunk> rawChunks
List<ScriptChunk> redeemScriptChunks
) {
super(
scriptType,
redeemScriptChunks.subList(2, redeemScriptChunks.size()),
rawChunks
redeemScriptChunks.subList(2, redeemScriptChunks.size())
);
this.multiSigType = MultiSigType.FAST_BRIDGE_MULTISIG;
this.derivationHash = redeemScriptChunks.get(0).data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,11 @@ public MultiSigType getMultiSigType() {
return MultiSigType.NO_MULTISIG_TYPE;
}

@Override
public ScriptType getScriptType() {
return ScriptType.UNDEFINED;
}

@Override
public int getM() {
return -1;
}

@Override
public int getSigInsertionIndex(Sha256Hash hash, BtcECKey signingKey) {
return 0;
}

@Override
public int findKeyInRedeem(BtcECKey key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,10 @@ public class P2shErpFederationRedeemScriptParser extends StandardRedeemScriptPar
public static long MAX_CSV_VALUE = 65_535L; // 2^16 - 1, since bitcoin will interpret up to 16 bits as the CSV value

public P2shErpFederationRedeemScriptParser(
ScriptType scriptType,
List<ScriptChunk> redeemScriptChunks,
List<ScriptChunk> rawChunks
List<ScriptChunk> redeemScriptChunks
) {
super(
scriptType,
extractStandardRedeemScript(redeemScriptChunks).getChunks(),
rawChunks
extractStandardRedeemScript(redeemScriptChunks).getChunks()
);
this.multiSigType = MultiSigType.P2SH_ERP_FED;
}
Expand Down
8 changes: 0 additions & 8 deletions src/main/java/co/rsk/bitcoinj/script/RedeemScriptParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,12 @@ enum MultiSigType {
FAST_BRIDGE_P2SH_ERP_FED
}

enum ScriptType {
P2SH,
REDEEM_SCRIPT,
UNDEFINED
}

MultiSigType getMultiSigType();

ScriptType getScriptType();

int getM();

int getSigInsertionIndex(Sha256Hash hash, BtcECKey signingKey);

int findKeyInRedeem(BtcECKey key);

List<BtcECKey> getPubKeys();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package co.rsk.bitcoinj.script;

import co.rsk.bitcoinj.core.ScriptException;
import co.rsk.bitcoinj.core.Utils;
import co.rsk.bitcoinj.script.RedeemScriptParser.ScriptType;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -27,98 +25,44 @@ public static RedeemScriptParser get(List<ScriptChunk> chunks) {
return new NoRedeemScriptParser();
}

ParseResult result = extractRedeemScriptFromChunks(chunks);

if (result == null) {
return new NoRedeemScriptParser();
}

if (FastBridgeRedeemScriptParser.isFastBridgeMultiSig(result.internalScript)) {
if (FastBridgeRedeemScriptParser.isFastBridgeMultiSig(chunks)) {
logger.debug("[get] Return FastBridgeRedeemScriptParser");
return new FastBridgeRedeemScriptParser(
result.scriptType,
result.internalScript,
chunks
);
}
if (StandardRedeemScriptParser.isStandardMultiSig(result.internalScript)) {
if (StandardRedeemScriptParser.isStandardMultiSig(chunks)) {
logger.debug("[get] Return StandardRedeemScriptParser");
return new StandardRedeemScriptParser(
result.scriptType,
result.internalScript,
chunks
);
}
if (P2shErpFederationRedeemScriptParser.isP2shErpFed(result.internalScript)) {
if (P2shErpFederationRedeemScriptParser.isP2shErpFed(chunks)) {
logger.debug("[get] Return P2shErpFederationRedeemScriptParser");
return new P2shErpFederationRedeemScriptParser(
result.scriptType,
result.internalScript,
chunks
);
}
if (FastBridgeP2shErpRedeemScriptParser.isFastBridgeP2shErpFed(result.internalScript)) {
if (FastBridgeP2shErpRedeemScriptParser.isFastBridgeP2shErpFed(chunks)) {
logger.debug("[get] Return FastBridgeP2shErpRedeemScriptParser");
return new FastBridgeP2shErpRedeemScriptParser(
result.scriptType,
result.internalScript,
chunks
);
}
if (ErpFederationRedeemScriptParser.isErpFed(result.internalScript)) {
if (ErpFederationRedeemScriptParser.isErpFed(chunks)) {
logger.debug("[get] Return ErpFederationRedeemScriptParser");
return new ErpFederationRedeemScriptParser(
result.scriptType,
result.internalScript,
chunks
);
}
if (FastBridgeErpRedeemScriptParser.isFastBridgeErpFed(result.internalScript)) {
if (FastBridgeErpRedeemScriptParser.isFastBridgeErpFed(chunks)) {
logger.debug("[get] Return FastBridgeErpRedeemScriptParser");
return new FastBridgeErpRedeemScriptParser(
result.scriptType,
result.internalScript,
chunks
);
}

logger.debug("[get] Return NoRedeemScriptParser");
return new NoRedeemScriptParser();
}

private static ParseResult extractRedeemScriptFromChunks(List<ScriptChunk> chunks) {
ScriptChunk lastChunk = chunks.get(chunks.size() - 1);
if (RedeemScriptValidator.isRedeemLikeScript(chunks)) {
return new ParseResult(chunks, ScriptType.REDEEM_SCRIPT);
}
if (lastChunk.data != null && lastChunk.data.length > 0) {
int lastByte = lastChunk.data[lastChunk.data.length - 1] & 0xff;
// ERP and standard (+fastBridge) finish with OP_CHECKMULTISIG and P2SHERP (+fastBridge) finish with OP_ENDIF
if (
lastByte == ScriptOpCodes.OP_CHECKMULTISIG ||
lastByte == ScriptOpCodes.OP_CHECKMULTISIGVERIFY ||
lastByte == ScriptOpCodes.OP_ENDIF
) {
ScriptParserResult result = ScriptParser.parseScriptProgram(lastChunk.data);
if (result.getException().isPresent()) {
String message = String.format("Error trying to parse inner script. %s", result.getException().get());
logger.debug("[extractRedeemScriptFromChunks] {}", message);
throw new ScriptException(message);
}
return new ParseResult(result.getChunks(), ScriptType.P2SH);
}
}
logger.debug("[extractRedeemScriptFromChunks] Could not get redeem script from given chunks");
return null;
}

private static class ParseResult {
public final List<ScriptChunk> internalScript;
public final ScriptType scriptType;

public ParseResult(List<ScriptChunk> internalScript, ScriptType scriptType) {
this.internalScript = internalScript;
this.scriptType = scriptType;
}
}
}
40 changes: 40 additions & 0 deletions src/main/java/co/rsk/bitcoinj/script/RedeemScriptUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package co.rsk.bitcoinj.script;

import co.rsk.bitcoinj.core.ScriptException;

import java.util.List;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RedeemScriptUtils {
private static final Logger logger = LoggerFactory.getLogger(RedeemScriptUtils.class);

private RedeemScriptUtils() { }
public static Optional<RedeemScriptParser> extractRedeemScriptParserFromInputScript(Script inputScript) {
marcos-iov marked this conversation as resolved.
Show resolved Hide resolved
List<ScriptChunk> chunks = inputScript.getChunks();

if (chunks == null || chunks.isEmpty()) {
return Optional.empty();
}

byte[] program = chunks.get(chunks.size() - 1).data;
if (program == null) {
return Optional.empty();
}

try {
Script redeemScript = new Script(program);
RedeemScriptParser redeemScriptParser = RedeemScriptParserFactory.get(redeemScript.getChunks());
return Optional.of(redeemScriptParser);
} catch (ScriptException e) {
logger.debug(
"[extractRedeemScriptFromInput] Failed to extract redeem script from inputScript {}. {}",
inputScript,
e.getMessage()
);
return Optional.empty();
}
}
}
43 changes: 33 additions & 10 deletions src/main/java/co/rsk/bitcoinj/script/Script.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import static co.rsk.bitcoinj.script.ScriptOpCodes.*;
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;
Expand Down Expand Up @@ -120,7 +121,6 @@ private Script() {
Script(List<ScriptChunk> chunks) {
this.chunks = Collections.unmodifiableList(new ArrayList<>(chunks));
creationTimeSeconds = Utils.currentTimeSeconds();
redeemScriptParser = RedeemScriptParserFactory.get(this.chunks);
}

/**
Expand All @@ -132,15 +132,12 @@ public Script(byte[] programBytes) throws ScriptException {
program = programBytes;
parse(programBytes);
creationTimeSeconds = 0;
redeemScriptParser = RedeemScriptParserFactory.get(this.chunks);

}

public Script(byte[] programBytes, long creationTimeSeconds) throws ScriptException {
program = programBytes;
parse(programBytes);
this.creationTimeSeconds = creationTimeSeconds;
redeemScriptParser = RedeemScriptParserFactory.get(this.chunks);
}

public long getCreationTimeSeconds() {
Expand Down Expand Up @@ -485,24 +482,50 @@ public Script getScriptSigWithSignature(Script scriptSig, byte[] sigBytes, int i
return ScriptBuilder.updateScriptWithSignature(scriptSig, sigBytes, index, sigsPrefixCount, sigsSuffixCount);
}

private RedeemScriptParser getRedeemScriptParser() {
if (redeemScriptParser == null){
redeemScriptParser = RedeemScriptParserFactory.get(chunks);
}
return redeemScriptParser;
}

/**
* Returns the index where a signature by the key should be inserted. Only applicable to
* a P2SH scriptSig.
*/
public int getSigInsertionIndex(Sha256Hash hash, BtcECKey signingKey) {
return this.redeemScriptParser.getSigInsertionIndex(hash, signingKey);
// Iterate over existing signatures, skipping the initial OP_0, the final redeem script
// and any placeholder OP_0 sigs.
List<ScriptChunk> existingChunks = chunks.subList(1, chunks.size() - 1);
ScriptChunk redeemScriptChunk = chunks.get(chunks.size() - 1);
checkNotNull(redeemScriptChunk.data);
Script redeemScript = new Script(redeemScriptChunk.data);

int sigCount = 0;
int myIndex = redeemScript.findKeyInRedeem(signingKey);
for (ScriptChunk chunk : existingChunks) {
if (chunk.opcode != OP_0) {
checkNotNull(chunk.data);
if (myIndex < redeemScript.findSigInRedeem(chunk.data, hash)) {
return sigCount;
}
sigCount++;
}
}

return sigCount;
}

public int findKeyInRedeem(BtcECKey key) {
return this.redeemScriptParser.findKeyInRedeem(key);
return this.getRedeemScriptParser().findKeyInRedeem(key);
}

public int findSigInRedeem(byte[] signatureBytes, Sha256Hash hash) {
return this.redeemScriptParser.findSigInRedeem(signatureBytes, hash);
return this.getRedeemScriptParser().findSigInRedeem(signatureBytes, hash);
}

public List<BtcECKey> getPubKeys() throws ScriptException {
return this.redeemScriptParser.getPubKeys();
return this.getRedeemScriptParser().getPubKeys();
}

////////////////////// Interface used during verification of transactions/blocks ////////////////////////////////
Expand Down Expand Up @@ -591,7 +614,7 @@ public static long getP2SHSigOpCount(byte[] scriptSig) throws ScriptException {
public int getNumberOfSignaturesRequiredToSpend() {
if (this.isSentToMultiSig()) {
// for M of N CHECKMULTISIG script we will need M signatures to spend
return redeemScriptParser.getM();
return this.getRedeemScriptParser().getM();
} else if (isSentToAddress() || isSentToRawPubKey()) {
// pay-to-address and pay-to-pubkey require single sig
return 1;
Expand Down Expand Up @@ -654,7 +677,7 @@ public boolean isPayToScriptHash() {
* Returns whether this script matches the format used for multisig outputs: [n] [keys...] [m] CHECKMULTISIG
*/
public boolean isSentToMultiSig() {
return !redeemScriptParser.getMultiSigType().equals(MultiSigType.NO_MULTISIG_TYPE);
return !this.getRedeemScriptParser().getMultiSigType().equals(MultiSigType.NO_MULTISIG_TYPE);
}

public boolean isSentToCLTVPaymentChannel() {
Expand Down
Loading