diff --git a/pom.xml b/pom.xml
index 8a1852d7c..c7d66c67c 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-14
+ 0.14.4-rsk-14-SNAPSHOT
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 c3c5cc4bd..5534bd5c6 100644
--- a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java
+++ b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java
@@ -60,7 +60,7 @@ public static TransactionWitness createWitness(@Nullable final TransactionSignat
public static TransactionWitness createWitnessScript(Script witnessScript, List signatures) {
List pushes = new ArrayList<>(signatures.size() + 2);
- pushes.add(new byte[] {});
+ //pushes.add(new byte[] {});
for (TransactionSignature signature : signatures) {
pushes.add(signature.encodeToBitcoin());
}
@@ -68,6 +68,47 @@ public static TransactionWitness createWitnessScript(Script witnessScript, List<
return TransactionWitness.of(pushes);
}
+ public static TransactionWitness createWitnessScriptWithNewRedeem(Script witnessScript, List thresholdSignatures, int signaturesSize) {
+ int zeroSignaturesSize = signaturesSize - thresholdSignatures.size();
+ List pushes = new ArrayList<>(signaturesSize + 1);
+ for (int i = 0; i < thresholdSignatures.size(); i++) {
+ pushes.add(thresholdSignatures.get(i).encodeToBitcoin());
+ }
+ for (int i = 0; i < zeroSignaturesSize; i ++) {
+ pushes.add(new byte[0]);
+ }
+ pushes.add(witnessScript.getProgram());
+ return TransactionWitness.of(pushes);
+ }
+
+ public static TransactionWitness createWitnessErpScriptWithNewRedeemStandard(Script witnessScript, List thresholdSignatures, int signaturesSize) {
+ int zeroSignaturesSize = signaturesSize - thresholdSignatures.size();
+ List pushes = new ArrayList<>(signaturesSize + 2);
+ for (int i = 0; i < thresholdSignatures.size(); i++) {
+ pushes.add(thresholdSignatures.get(i).encodeToBitcoin());
+ }
+ for (int i = 0; i < zeroSignaturesSize; i ++) {
+ pushes.add(new byte[0]);
+ }
+ pushes.add(new byte[] {}); // OP_NOTIF argument
+ pushes.add(witnessScript.getProgram());
+ return TransactionWitness.of(pushes);
+ }
+
+ public static TransactionWitness createWitnessErpScriptWithNewRedeemEmergency(Script witnessScript, List thresholdSignatures, int signaturesSize) {
+ int zeroSignaturesSize = signaturesSize - thresholdSignatures.size();
+ List pushes = new ArrayList<>(signaturesSize + 2);
+ for (int i = 0; i < thresholdSignatures.size(); i++) {
+ pushes.add(thresholdSignatures.get(i).encodeToBitcoin());
+ }
+ for (int i = 0; i < zeroSignaturesSize; i ++) {
+ pushes.add(new byte[0]);
+ }
+ pushes.add(new byte[] {1}); // OP_NOTIF argument
+ pushes.add(witnessScript.getProgram());
+ return TransactionWitness.of(pushes);
+ }
+
public static TransactionWitness createWitnessErpScript(Script witnessScript, List signatures) {
List pushes = new ArrayList<>(signatures.size() + 3);
pushes.add(new byte[] {});
diff --git a/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java b/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java
index d31e71b78..c6d67fd9d 100644
--- a/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java
+++ b/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java
@@ -133,6 +133,43 @@ public static Script createP2shP2wshErpRedeemScriptWithFlyover(
return erpP2shP2wshRedeemScript;
}
+ public static Script createErpP2shP2wshNewRedeemScript(
+ Script defaultFederationRedeemScript,
+ Script erpFederationRedeemScript,
+ Long csvValue) {
+
+/* validateErpRedeemScriptValues(
+ defaultFederationRedeemScript,
+ erpFederationRedeemScript,
+ csvValue
+ );*/
+ byte[] serializedCsvValue = Utils.signedLongToByteArrayLE(csvValue);
+
+ ScriptBuilder scriptBuilder = new ScriptBuilder();
+
+ Script erpRedeemScript = scriptBuilder
+ .op(ScriptOpCodes.OP_NOTIF)
+ .addChunks(defaultFederationRedeemScript.getChunks())
+ .op(ScriptOpCodes.OP_ELSE)
+ .data(serializedCsvValue)
+ .op(ScriptOpCodes.OP_CHECKSEQUENCEVERIFY)
+ .op(ScriptOpCodes.OP_DROP)
+ .addChunks(erpFederationRedeemScript.getChunks())
+ .op(ScriptOpCodes.OP_ENDIF)
+ .build();
+
+/* // Validate the created redeem script has a valid structure
+ if (!RedeemScriptValidator.hasErpRedeemScriptStructure(erpRedeemScript.getChunks())) {
+ String message = String.format(
+ "Created redeem script has an invalid structure, not ERP redeem script. Redeem script created: %s",
+ erpRedeemScript
+ );
+ logger.debug("[createErpRedeemScript] {}", message);
+ throw new VerificationException(message);
+ }*/
+ return erpRedeemScript;
+ }
+
public static boolean isP2shErpFed(List chunks) {
return RedeemScriptValidator.hasP2shErpRedeemScriptStructure(chunks);
}
diff --git a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java
index 120516f29..dafd6101c 100644
--- a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java
+++ b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java
@@ -282,6 +282,25 @@ public static Script createMultiSigOutputScript(int threshold, List pu
return builder.build();
}
+ /** Creates a program that requires at least N of the given keys to sign, using OP_CHECKSIG, OP_SWAP and OP_ADD. */
+ public static Script createNewMultiSigOutputScript(int threshold, List pubkeys) {
+ checkArgument(threshold > 0);
+ checkArgument(threshold <= pubkeys.size());
+ ScriptBuilder builder = new ScriptBuilder();
+ BtcECKey lastKey = pubkeys.get(pubkeys.size() - 1);
+ builder.data(lastKey.getPubKey());
+ builder.op(OP_CHECKSIG);
+ for (int i = pubkeys.size() - 2; i >= 0; i --) {
+ builder.op(OP_SWAP);
+ builder.data(pubkeys.get(i).getPubKey());
+ builder.op(OP_CHECKSIG);
+ builder.op(OP_ADD);
+ }
+ builder.number(threshold);
+ builder.op(OP_NUMEQUAL);
+ return builder.build();
+ }
+
/** Create a program that satisfies an OP_CHECKMULTISIG program. */
public static Script createMultiSigInputScript(List signatures) {
List sigs = new ArrayList(signatures.size());
@@ -448,6 +467,12 @@ public static Script createRedeemScript(int threshold, List pubkeys) {
return ScriptBuilder.createMultiSigOutputScript(threshold, pubkeys);
}
+ public static Script createNewRedeemScript(int threshold, List pubkeys) {
+ pubkeys = new ArrayList(pubkeys);
+ Collections.sort(pubkeys, BtcECKey.PUBKEY_COMPARATOR);
+ return ScriptBuilder.createNewMultiSigOutputScript(threshold, pubkeys);
+ }
+
/**
* Creates a script of the form OP_RETURN [data]. This feature allows you to attach a small piece of data (like
* a hash of something stored elsewhere) to a zero valued output which can never be spent and thus does not pollute
diff --git a/src/test/java/co/rsk/bitcoinj/script/NewRedeemScriptTest.java b/src/test/java/co/rsk/bitcoinj/script/NewRedeemScriptTest.java
new file mode 100644
index 000000000..b349e9659
--- /dev/null
+++ b/src/test/java/co/rsk/bitcoinj/script/NewRedeemScriptTest.java
@@ -0,0 +1,100 @@
+package co.rsk.bitcoinj.script;
+
+import static co.rsk.bitcoinj.script.Script.ALL_VERIFY_FLAGS;
+
+import co.rsk.bitcoinj.core.Address;
+import co.rsk.bitcoinj.core.BtcECKey;
+import co.rsk.bitcoinj.core.BtcTransaction;
+import co.rsk.bitcoinj.core.Coin;
+import co.rsk.bitcoinj.core.NetworkParameters;
+import co.rsk.bitcoinj.core.Sha256Hash;
+import co.rsk.bitcoinj.crypto.TransactionSignature;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.spongycastle.util.encoders.Hex;
+
+public class NewRedeemScriptTest {
+ @Test
+ public void spendFromP2shP2wshAddressWithNewRedeem() {
+ NetworkParameters networkParameters = NetworkParameters.fromID(NetworkParameters.ID_TESTNET);
+
+ // Created with GenNodeKeyId using seed 'fed1'
+ byte[] publicKeyBytes = Hex.decode("043267e382e076cbaa199d49ea7362535f95b135de181caf66b391f541bf39ab0e75b8577faac2183782cb0d76820cf9f356831d216e99d886f8a6bc47fe696939");
+ BtcECKey btcKey1 = BtcECKey.fromPublicOnly(publicKeyBytes);
+ BtcECKey fed1PrivKey = BtcECKey.fromPrivate(Hex.decode("529822842595a3a6b3b3e51e9cffa0db66452599f7beec542382a02b1e42be4b"));
+
+ // Created with GenNodeKeyId using seed 'fed3', used for fed2 to keep keys sorted
+ publicKeyBytes = Hex.decode("0443e106d90183e2eef7d5cb7538a634439bf1301d731787c6736922ff19e750ed39e74a76731fed620aeedbcd77e4de403fc4148efd3b5dbfc6cef550aa63c377");
+ BtcECKey btcKey2 = BtcECKey.fromPublicOnly(publicKeyBytes);
+ BtcECKey fed2PrivKey = BtcECKey.fromPrivate(Hex.decode("b2889610e66cd3f7de37c81c20c786b576349b80b3f844f8409e3a29d95c0c7c"));
+
+ // Created with GenNodeKeyId using seed 'fed2', used for fed3 to keep keys sorted
+ publicKeyBytes = Hex.decode("04bd5b51b1c5d799da190285c8078a2712b8e5dc6f73c799751e6256bb89a4bd04c6444b00289fc76ee853fcfa52b3083d66c42e84f8640f53a4cdf575e4d4a399");
+ BtcECKey btcKey3 = BtcECKey.fromPublicOnly(publicKeyBytes);
+ BtcECKey fed3PrivKey = BtcECKey.fromPrivate(Hex.decode("fa013890aa14dd269a0ca16003cabde1688021358b662d17b1e8c555f5cccc6e"));
+
+ List keys = Arrays.asList(btcKey1, btcKey2, btcKey3);
+ List privateKeys = Arrays.asList(fed1PrivKey, fed2PrivKey, fed3PrivKey);
+
+ Script redeemScript = new ScriptBuilder().createNewRedeemScript(keys.size() / 2 + 1, keys);
+
+ Script p2shOutputScript = ScriptBuilder.createP2SHOutputScript(redeemScript);
+ Address legacyAddress = Address.fromP2SHScript(
+ networkParameters,
+ p2shOutputScript
+ );
+ System.out.println(legacyAddress);
+
+
+ Sha256Hash fundTxHash = Sha256Hash.wrap("7728d0ad5126afe126fe39243da57a42d66effdadda0f5825fafbd51ac1bb7ef");
+ int outputIndex = 0;
+ Address destinationAddress = Address.fromBase58(networkParameters, "msgc5Gtz2L9MVhXPDrFRCYPa16QgoZ2EjP"); // testnet
+ Coin value = Coin.valueOf(10_000);
+ Coin fee = Coin.valueOf(1_000);
+
+ BtcTransaction spendTx = new BtcTransaction(networkParameters);
+ spendTx.addInput(fundTxHash, outputIndex, new Script(new byte[]{}));
+ spendTx.addOutput(value.minus(fee), destinationAddress);
+ spendTx.setVersion(2);
+
+ // Create signatures
+ int inputIndex = 0;
+ Sha256Hash sigHash = spendTx.hashForSignature(
+ inputIndex,
+ redeemScript,
+ BtcTransaction.SigHash.ALL,
+ false
+ );
+
+ int requiredSignatures = privateKeys.size() / 2 + 1;
+ List signatures = new ArrayList<>();
+
+ for (int i = 0; i < requiredSignatures; i++) {
+ BtcECKey keyToSign = privateKeys.get(i);
+ BtcECKey.ECDSASignature signature = keyToSign.sign(sigHash);
+ TransactionSignature txSignature = new TransactionSignature(
+ signature,
+ BtcTransaction.SigHash.ALL,
+ false
+ );
+ signatures.add(txSignature);
+ }
+
+ ScriptBuilder scriptBuilder = new ScriptBuilder();
+ Script scriptSig = scriptBuilder
+ .data(TransactionSignature.dummy().encodeToBitcoin())
+ .data(signatures.get(1).encodeToBitcoin())
+ .data(signatures.get(0).encodeToBitcoin())
+ .data(redeemScript.getProgram())
+ .build();
+
+ spendTx.getInput(0).setScriptSig(scriptSig);
+ scriptSig.correctlySpends(spendTx, 0, p2shOutputScript, ALL_VERIFY_FLAGS);
+
+ // Uncomment to print the raw tx in console and broadcast https://blockstream.info/testnet/tx/push
+ System.out.println(Hex.toHexString(spendTx.bitcoinSerialize()));
+ }
+}
+