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())); + } +} +