From e1095b56343942eb24987573319bbfcd5248b3a4 Mon Sep 17 00:00:00 2001 From: Marcos Date: Fri, 21 Jul 2023 16:43:44 -0300 Subject: [PATCH 01/20] Create new method createP2SHP2WSHOutputScript --- src/main/java/co/rsk/bitcoinj/core/Utils.java | 14 +++++-- .../co/rsk/bitcoinj/script/ScriptBuilder.java | 18 +++++++++ .../bitcoinj/script/P2shP2WSHScriptTest.java | 38 +++++++++++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 src/test/java/co/rsk/bitcoinj/script/P2shP2WSHScriptTest.java diff --git a/src/main/java/co/rsk/bitcoinj/core/Utils.java b/src/main/java/co/rsk/bitcoinj/core/Utils.java index bde1d354e..80358695e 100644 --- a/src/main/java/co/rsk/bitcoinj/core/Utils.java +++ b/src/main/java/co/rsk/bitcoinj/core/Utils.java @@ -267,11 +267,19 @@ public static int readUint16BE(byte[] bytes, int offset) { */ public static byte[] sha256hash160(byte[] input) { byte[] sha256 = Sha256Hash.hash(input); + return hash160(sha256); + } + + public static byte[] hash160(byte[] input) { + return digestRipeMd160(Sha256Hash.hash(input)); + } + + public static byte[] digestRipeMd160(byte[] sha256) { RIPEMD160Digest digest = new RIPEMD160Digest(); digest.update(sha256, 0, sha256.length); - byte[] out = new byte[20]; - digest.doFinal(out, 0); - return out; + byte[] ripmemdHash = new byte[20]; + digest.doFinal(ripmemdHash, 0); + return ripmemdHash; } /** diff --git a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java index ed69f8948..be11e1c21 100644 --- a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java +++ b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java @@ -17,6 +17,7 @@ package co.rsk.bitcoinj.script; import co.rsk.bitcoinj.core.BtcTransaction; +import co.rsk.bitcoinj.core.Sha256Hash; import com.google.common.collect.Lists; import co.rsk.bitcoinj.core.Address; import co.rsk.bitcoinj.core.BtcECKey; @@ -415,6 +416,23 @@ public static Script createP2SHOutputScript(Script redeemScript) { return ScriptBuilder.createP2SHOutputScript(hash); } + /** + * Creates a P2SH-P2WSH scriptPubKey for the given redeem script. + */ + public static Script createP2SHP2WSHOutputScript(Script redeemScript) { + + byte[] redeemScriptHash = Sha256Hash.hash(redeemScript.getProgram()); + + Script witnessScript = new ScriptBuilder() + .number(ScriptOpCodes.OP_0) + .data(redeemScriptHash) + .build(); + + byte[] outputScriptHash = Utils.hash160(witnessScript.getProgram()); + + return ScriptBuilder.createP2SHOutputScript(outputScriptHash); + } + /** * Creates a P2SH output script with given public keys and threshold. Given public keys will be placed in * redeem script in the lexicographical sorting order. diff --git a/src/test/java/co/rsk/bitcoinj/script/P2shP2WSHScriptTest.java b/src/test/java/co/rsk/bitcoinj/script/P2shP2WSHScriptTest.java new file mode 100644 index 000000000..a11351eb0 --- /dev/null +++ b/src/test/java/co/rsk/bitcoinj/script/P2shP2WSHScriptTest.java @@ -0,0 +1,38 @@ +package co.rsk.bitcoinj.script; + +import co.rsk.bitcoinj.core.Address; +import co.rsk.bitcoinj.core.BtcECKey; +import co.rsk.bitcoinj.core.NetworkParameters; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.Assert; +import org.junit.Test; +import org.spongycastle.util.encoders.Hex; + +public class P2shP2WSHScriptTest { + + @Test + public void getAddressFromP2shP2wshScript() { + List keys = Arrays.asList(new String[]{ + "027de2af71862e0c64bf0ec5a66e3abc3b01fc57877802e6a6a81f6ea1d3561007", + "02d9c67fef9f8d0707cbcca195eb5f26c6a65da6ca2d6130645c434bb924063856", + "0346f033b8652a17d319d3ecbbbf20fd2cd663a6548173b9419d8228eef095012e" + }).stream().map(k -> BtcECKey.fromPublicOnly(Hex.decode(k))).collect(Collectors.toList()); + + Script redeemNuestro = new ScriptBuilder().createRedeemScript( + keys.size() / 2 + 1, + keys + ); + + Script p2SHP2WSHOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(redeemNuestro); + Address segwitAddress = Address.fromP2SHScript( + NetworkParameters.fromID(NetworkParameters.ID_TESTNET), + p2SHP2WSHOutputScript + ); + + Assert.assertEquals("2NCQHJJuG2iQjN2Be3QYXwWvFgS6AZ4MEkL", segwitAddress.toBase58()); + // https://mempool.space/testnet/tx/1744459aeaf7369aadc9fc40de9ab2bf575b14e35029b35a7ee4bbd3de65af7f + } + +} From cb9577c59be0a4ca1b7b9699c2d9272363438cc4 Mon Sep 17 00:00:00 2001 From: julia zack Date: Mon, 24 Jul 2023 13:31:14 -0300 Subject: [PATCH 02/20] Replace sha256hash160 with hash160. Refactor --- src/main/java/co/rsk/bitcoinj/core/BtcECKey.java | 2 +- src/main/java/co/rsk/bitcoinj/core/Utils.java | 12 ++++-------- .../co/rsk/bitcoinj/crypto/DeterministicKey.java | 2 +- src/main/java/co/rsk/bitcoinj/script/Script.java | 4 ++-- .../java/co/rsk/bitcoinj/script/ScriptBuilder.java | 6 +----- ...P2WSHScriptTest.java => P2shP2wshScriptTest.java} | 8 ++++---- src/test/java/co/rsk/bitcoinj/script/ScriptTest.java | 2 +- 7 files changed, 14 insertions(+), 22 deletions(-) rename src/test/java/co/rsk/bitcoinj/script/{P2shP2WSHScriptTest.java => P2shP2wshScriptTest.java} (83%) diff --git a/src/main/java/co/rsk/bitcoinj/core/BtcECKey.java b/src/main/java/co/rsk/bitcoinj/core/BtcECKey.java index c28dbd3b2..fbd338b0c 100644 --- a/src/main/java/co/rsk/bitcoinj/core/BtcECKey.java +++ b/src/main/java/co/rsk/bitcoinj/core/BtcECKey.java @@ -432,7 +432,7 @@ public static ECPoint publicPointFromPrivate(BigInteger privKey) { /** Gets the hash160 form of the public key (as seen in addresses). */ public byte[] getPubKeyHash() { if (pubKeyHash == null) - pubKeyHash = Utils.sha256hash160(this.pub.getEncoded()); + pubKeyHash = Utils.hash160(this.pub.getEncoded()); return pubKeyHash; } diff --git a/src/main/java/co/rsk/bitcoinj/core/Utils.java b/src/main/java/co/rsk/bitcoinj/core/Utils.java index 80358695e..fd7312a5c 100644 --- a/src/main/java/co/rsk/bitcoinj/core/Utils.java +++ b/src/main/java/co/rsk/bitcoinj/core/Utils.java @@ -263,12 +263,8 @@ public static int readUint16BE(byte[] bytes, int offset) { } /** - * Calculates RIPEMD160(SHA256(input)). This is used in Address calculations. + * Hash160 calculates RIPEMD160(SHA256(input)). This is used in Address calculations. */ - public static byte[] sha256hash160(byte[] input) { - byte[] sha256 = Sha256Hash.hash(input); - return hash160(sha256); - } public static byte[] hash160(byte[] input) { return digestRipeMd160(Sha256Hash.hash(input)); @@ -277,9 +273,9 @@ public static byte[] hash160(byte[] input) { public static byte[] digestRipeMd160(byte[] sha256) { RIPEMD160Digest digest = new RIPEMD160Digest(); digest.update(sha256, 0, sha256.length); - byte[] ripmemdHash = new byte[20]; - digest.doFinal(ripmemdHash, 0); - return ripmemdHash; + byte[] ripemdHash = new byte[20]; + digest.doFinal(ripemdHash, 0); + return ripemdHash; } /** diff --git a/src/main/java/co/rsk/bitcoinj/crypto/DeterministicKey.java b/src/main/java/co/rsk/bitcoinj/crypto/DeterministicKey.java index 74c79b78d..cdec060c1 100644 --- a/src/main/java/co/rsk/bitcoinj/crypto/DeterministicKey.java +++ b/src/main/java/co/rsk/bitcoinj/crypto/DeterministicKey.java @@ -212,7 +212,7 @@ public byte[] getChainCode() { * Returns RIPE-MD160(SHA256(pub key bytes)). */ public byte[] getIdentifier() { - return Utils.sha256hash160(getPubKey()); + return Utils.hash160(getPubKey()); } /** Returns the first 32 bits of the result of {@link #getIdentifier()}. */ diff --git a/src/main/java/co/rsk/bitcoinj/script/Script.java b/src/main/java/co/rsk/bitcoinj/script/Script.java index 1cea8b02f..dffab60cc 100644 --- a/src/main/java/co/rsk/bitcoinj/script/Script.java +++ b/src/main/java/co/rsk/bitcoinj/script/Script.java @@ -325,7 +325,7 @@ public BigInteger getCLTVPaymentChannelExpiry() { */ @Deprecated public Address getFromAddress(NetworkParameters params) throws ScriptException { - return new Address(params, Utils.sha256hash160(getPubKey())); + return new Address(params, Utils.hash160(getPubKey())); } /** @@ -1263,7 +1263,7 @@ public static void executeScript(@Nullable BtcTransaction txContainingThis, long case OP_HASH160: if (stack.size() < 1) throw new ScriptException("Attempted OP_HASH160 on an empty stack"); - stack.add(Utils.sha256hash160(stack.pollLast())); + stack.add(Utils.hash160(stack.pollLast())); break; case OP_HASH256: if (stack.size() < 1) diff --git a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java index be11e1c21..077737a85 100644 --- a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java +++ b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java @@ -412,7 +412,7 @@ public static Script createP2SHOutputScript(byte[] hash) { * Creates a scriptPubKey for the given redeem script. */ public static Script createP2SHOutputScript(Script redeemScript) { - byte[] hash = Utils.sha256hash160(redeemScript.getProgram()); + byte[] hash = Utils.hash160(redeemScript.getProgram()); return ScriptBuilder.createP2SHOutputScript(hash); } @@ -420,16 +420,12 @@ public static Script createP2SHOutputScript(Script redeemScript) { * Creates a P2SH-P2WSH scriptPubKey for the given redeem script. */ public static Script createP2SHP2WSHOutputScript(Script redeemScript) { - byte[] redeemScriptHash = Sha256Hash.hash(redeemScript.getProgram()); - Script witnessScript = new ScriptBuilder() .number(ScriptOpCodes.OP_0) .data(redeemScriptHash) .build(); - byte[] outputScriptHash = Utils.hash160(witnessScript.getProgram()); - return ScriptBuilder.createP2SHOutputScript(outputScriptHash); } diff --git a/src/test/java/co/rsk/bitcoinj/script/P2shP2WSHScriptTest.java b/src/test/java/co/rsk/bitcoinj/script/P2shP2wshScriptTest.java similarity index 83% rename from src/test/java/co/rsk/bitcoinj/script/P2shP2WSHScriptTest.java rename to src/test/java/co/rsk/bitcoinj/script/P2shP2wshScriptTest.java index a11351eb0..5510e0d11 100644 --- a/src/test/java/co/rsk/bitcoinj/script/P2shP2WSHScriptTest.java +++ b/src/test/java/co/rsk/bitcoinj/script/P2shP2wshScriptTest.java @@ -10,7 +10,7 @@ import org.junit.Test; import org.spongycastle.util.encoders.Hex; -public class P2shP2WSHScriptTest { +public class P2shP2wshScriptTest { @Test public void getAddressFromP2shP2wshScript() { @@ -20,15 +20,15 @@ public void getAddressFromP2shP2wshScript() { "0346f033b8652a17d319d3ecbbbf20fd2cd663a6548173b9419d8228eef095012e" }).stream().map(k -> BtcECKey.fromPublicOnly(Hex.decode(k))).collect(Collectors.toList()); - Script redeemNuestro = new ScriptBuilder().createRedeemScript( + Script redeemScript = new ScriptBuilder().createRedeemScript( keys.size() / 2 + 1, keys ); - Script p2SHP2WSHOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(redeemNuestro); + Script p2shP2wshOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(redeemScript); Address segwitAddress = Address.fromP2SHScript( NetworkParameters.fromID(NetworkParameters.ID_TESTNET), - p2SHP2WSHOutputScript + p2shP2wshOutputScript ); Assert.assertEquals("2NCQHJJuG2iQjN2Be3QYXwWvFgS6AZ4MEkL", segwitAddress.toBase58()); diff --git a/src/test/java/co/rsk/bitcoinj/script/ScriptTest.java b/src/test/java/co/rsk/bitcoinj/script/ScriptTest.java index b5bf8f29e..070f29954 100644 --- a/src/test/java/co/rsk/bitcoinj/script/ScriptTest.java +++ b/src/test/java/co/rsk/bitcoinj/script/ScriptTest.java @@ -107,7 +107,7 @@ public void testScriptSig() { byte[] sigProgBytes = Hex.decode(sigProg); Script script = new Script(sigProgBytes); // Test we can extract the from address. - byte[] hash160 = Utils.sha256hash160(script.getPubKey()); + byte[] hash160 = Utils.hash160(script.getPubKey()); Address a = new Address(PARAMS, hash160); assertEquals("mkFQohBpy2HDXrCwyMrYL5RtfrmeiuuPY2", a.toString()); } From 11b918375e19a4801213c8f87138cb69e4011433 Mon Sep 17 00:00:00 2001 From: Marcos Date: Mon, 24 Jul 2023 16:11:23 -0300 Subject: [PATCH 03/20] Use createP2SHOutputScript implementation that receives the script --- src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java index 077737a85..a99d1d8bb 100644 --- a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java +++ b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java @@ -425,8 +425,8 @@ public static Script createP2SHP2WSHOutputScript(Script redeemScript) { .number(ScriptOpCodes.OP_0) .data(redeemScriptHash) .build(); - byte[] outputScriptHash = Utils.hash160(witnessScript.getProgram()); - return ScriptBuilder.createP2SHOutputScript(outputScriptHash); + + return ScriptBuilder.createP2SHOutputScript(witnessScript); } /** From e7fcfe73db441c55dd5a164d7c2ed9bcdf9d3a22 Mon Sep 17 00:00:00 2001 From: julia zack Date: Tue, 25 Jul 2023 09:15:35 -0300 Subject: [PATCH 04/20] Add test for function hash160 --- .../java/co/rsk/bitcoinj/core/UtilsTest.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/test/java/co/rsk/bitcoinj/core/UtilsTest.java b/src/test/java/co/rsk/bitcoinj/core/UtilsTest.java index 0c1fece22..c583ba1da 100644 --- a/src/test/java/co/rsk/bitcoinj/core/UtilsTest.java +++ b/src/test/java/co/rsk/bitcoinj/core/UtilsTest.java @@ -18,7 +18,9 @@ package co.rsk.bitcoinj.core; import java.math.BigInteger; +import java.util.Arrays; import java.util.Date; +import java.util.List; import org.junit.Test; import org.spongycastle.util.encoders.Hex; @@ -230,5 +232,34 @@ public void signedLongToByteArrayLE() { reversedConversion = Utils.reverseBytes(conversion); // Turn into BE obtainedValue = new BigInteger(reversedConversion).longValue(); assertEquals(value, obtainedValue); - } + }; + + @Test + public void hash160() { + + List inputs = Arrays.asList( + Hex.decode(""), + Hex.decode("abcd"), + Hex.decode("00"), + Hex.decode("01"), + Hex.decode("0000"), + Hex.decode("ffff") + ); + List expectedHashes = Arrays.asList( + Hex.decode("b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"), + Hex.decode("4671c47a9d20c240a291661520d4af51df08fb0b"), + Hex.decode("9f7fd096d37ed2c0e3f7f0cfc924beef4ffceb68"), + Hex.decode("c51b66bced5e4491001bd702669770dccf440982"), + Hex.decode("e6c41bcc570872e88e58db7c940dc8d399e72aef"), + Hex.decode("e6abebacc6bf964f5131e80b241e3fe14bc3e156") + ); + + for (int i = 0; i < inputs.size(); i++) { + byte[] input = inputs.get(i); + byte[] expectedHash = expectedHashes.get(i); + + byte[] hashResult = Utils.hash160(input); + assertArrayEquals(expectedHash, hashResult); + } + }; } From 5bc92f6a30f111c81165f1ff780abc80e7e0640a Mon Sep 17 00:00:00 2001 From: julia zack Date: Thu, 27 Jul 2023 17:13:19 -0300 Subject: [PATCH 05/20] Add createWitnessScript and needed constructor values into TxWitness class --- .../rsk/bitcoinj/core/TransactionWitness.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java index d11eec1d4..4169390de 100644 --- a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java +++ b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java @@ -1,10 +1,12 @@ package co.rsk.bitcoinj.core; import co.rsk.bitcoinj.crypto.TransactionSignature; +import co.rsk.bitcoinj.script.Script; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class TransactionWitness { static TransactionWitness empty = new TransactionWitness(0); @@ -13,12 +15,22 @@ public static TransactionWitness getEmpty() { return empty; } - private List pushes; + private final List pushes; public TransactionWitness(int pushCount) { pushes = new ArrayList(Math.min(pushCount, Utils.MAX_INITIAL_ARRAY_LENGTH)); } + public static TransactionWitness of(List pushes) { + return new TransactionWitness(pushes); + } + + private TransactionWitness(List pushes) { + for (byte[] push : pushes) + Objects.requireNonNull(push); + this.pushes = pushes; + } + public byte[] getPush(int i) { return pushes.get(i); } @@ -46,6 +58,15 @@ public static TransactionWitness createWitness(@Nullable final TransactionSignat return witness; } + public static TransactionWitness createWitnessScript(Script witnessScript, List signatures) { + List pushes = new ArrayList<>(signatures.size() + 2); + pushes.add(new byte[] {}); + for (TransactionSignature signature : signatures) + pushes.add(signature.encodeToBitcoin()); + pushes.add(witnessScript.getProgram()); + return TransactionWitness.of(pushes); + } + public byte[] getScriptBytes() { if (getPushCount() == 0) return new byte[0]; From b5369f1637a260bef453b47859e2c32ee810e859 Mon Sep 17 00:00:00 2001 From: julia zack Date: Thu, 3 Aug 2023 17:19:13 -0300 Subject: [PATCH 06/20] Add hashForWitnessSignature, createWitnessErpScript and createP2shP2wshErpRedeemScript --- .../co/rsk/bitcoinj/core/BtcTransaction.java | 108 +++++++++++++++++- .../rsk/bitcoinj/core/TransactionWitness.java | 14 ++- .../P2shErpFederationRedeemScriptParser.java | 23 ++++ 3 files changed, 143 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/core/BtcTransaction.java b/src/main/java/co/rsk/bitcoinj/core/BtcTransaction.java index b0c55c40d..995ed687a 100644 --- a/src/main/java/co/rsk/bitcoinj/core/BtcTransaction.java +++ b/src/main/java/co/rsk/bitcoinj/core/BtcTransaction.java @@ -30,6 +30,7 @@ import com.google.common.primitives.Longs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.spongycastle.crypto.params.KeyParameter; import javax.annotation.Nullable; import java.io.*; @@ -105,7 +106,6 @@ public int compare(final BtcTransaction tx1, final BtcTransaction tx2) { private ArrayList outputs; private ArrayList witnesses; - private long lockTime; // This is either the time the transaction was broadcast as measured from the local clock, or the time from the @@ -1166,6 +1166,112 @@ public Sha256Hash hashForSignature(int inputIndex, byte[] connectedScript, byte } } + public synchronized Sha256Hash hashForWitnessSignature( + int inputIndex, + byte[] scriptCode, + Coin prevValue, + SigHash type, + boolean anyoneCanPay) { + int sigHash = TransactionSignature.calcSigHashValue(type, anyoneCanPay); + return hashForWitnessSignature(inputIndex, scriptCode, prevValue, (byte) sigHash); + } + + /** + *

Calculates a signature hash, that is, a hash of a simplified form of the transaction. How exactly the transaction + * is simplified is specified by the type and anyoneCanPay parameters.

+ * + *

This is a low level API and when using the regular {@link Wallet} class you don't have to call this yourself. + * When working with more complex transaction types and contracts, it can be necessary. When signing a Witness output + * the scriptCode should be the script encoded into the scriptSig field, for normal transactions, it's the + * scriptPubKey of the output you're signing for. (See BIP143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki)

+ * + * @param inputIndex input the signature is being calculated for. Tx signatures are always relative to an input. + * @param scriptCode the script that should be in the given input during signing. + * @param prevValue the value of the coin being spent + * @param type Should be SigHash.ALL + * @param anyoneCanPay should be false. + */ + public synchronized Sha256Hash hashForWitnessSignature( + int inputIndex, + Script scriptCode, + Coin prevValue, + SigHash type, + boolean anyoneCanPay) { + return hashForWitnessSignature(inputIndex, scriptCode.getProgram(), prevValue, type, anyoneCanPay); + } + + public synchronized Sha256Hash hashForWitnessSignature( + int inputIndex, + byte[] scriptCode, + Coin prevValue, + byte sigHashType){ + ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(length == UNKNOWN_LENGTH ? 256 : length + 4); + try { + byte[] hashPrevouts = new byte[32]; + byte[] hashSequence = new byte[32]; + byte[] hashOutputs = new byte[32]; + int basicSigHashType = sigHashType & 0x1f; + boolean anyoneCanPay = (sigHashType & SigHash.ANYONECANPAY.value) == SigHash.ANYONECANPAY.value; + boolean signAll = (basicSigHashType != SigHash.SINGLE.value) && (basicSigHashType != SigHash.NONE.value); + + if (!anyoneCanPay) { + ByteArrayOutputStream bosHashPrevouts = new UnsafeByteArrayOutputStream(256); + for (int i = 0; i < this.inputs.size(); ++i) { + bosHashPrevouts.write(this.inputs.get(i).getOutpoint().getHash().getReversedBytes()); + uint32ToByteStreamLE(this.inputs.get(i).getOutpoint().getIndex(), bosHashPrevouts); + } + hashPrevouts = Sha256Hash.hashTwice(bosHashPrevouts.toByteArray()); + } + + if (!anyoneCanPay && signAll) { + ByteArrayOutputStream bosSequence = new UnsafeByteArrayOutputStream(256); + for (int i = 0; i < this.inputs.size(); ++i) { + uint32ToByteStreamLE(this.inputs.get(i).getSequenceNumber(), bosSequence); + } + hashSequence = Sha256Hash.hashTwice(bosSequence.toByteArray()); + } + + if (signAll) { + ByteArrayOutputStream bosHashOutputs = new UnsafeByteArrayOutputStream(256); + for (int i = 0; i < this.outputs.size(); ++i) { + uint64ToByteStreamLE( + BigInteger.valueOf(this.outputs.get(i).getValue().getValue()), + bosHashOutputs + ); + bosHashOutputs.write(new VarInt(this.outputs.get(i).getScriptBytes().length).encode()); + bosHashOutputs.write(this.outputs.get(i).getScriptBytes()); + } + hashOutputs = Sha256Hash.hashTwice(bosHashOutputs.toByteArray()); + } else if (basicSigHashType == SigHash.SINGLE.value && inputIndex < outputs.size()) { + ByteArrayOutputStream bosHashOutputs = new UnsafeByteArrayOutputStream(256); + uint64ToByteStreamLE( + BigInteger.valueOf(this.outputs.get(inputIndex).getValue().getValue()), + bosHashOutputs + ); + bosHashOutputs.write(new VarInt(this.outputs.get(inputIndex).getScriptBytes().length).encode()); + bosHashOutputs.write(this.outputs.get(inputIndex).getScriptBytes()); + hashOutputs = Sha256Hash.hashTwice(bosHashOutputs.toByteArray()); + } + uint32ToByteStreamLE(version, bos); + bos.write(hashPrevouts); + bos.write(hashSequence); + bos.write(inputs.get(inputIndex).getOutpoint().getHash().getReversedBytes()); + uint32ToByteStreamLE(inputs.get(inputIndex).getOutpoint().getIndex(), bos); + bos.write(new VarInt(scriptCode.length).encode()); + bos.write(scriptCode); + uint64ToByteStreamLE(BigInteger.valueOf(prevValue.getValue()), bos); + uint32ToByteStreamLE(inputs.get(inputIndex).getSequenceNumber(), bos); + bos.write(hashOutputs); + uint32ToByteStreamLE(this.lockTime, bos); + uint32ToByteStreamLE(0x000000ff & sigHashType, bos); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + + return Sha256Hash.twiceOf(bos.toByteArray()); + } + + @Override protected void bitcoinSerializeToStream(OutputStream stream) throws IOException { bitcoinSerializeToStream(stream, true); diff --git a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java index 4169390de..9267e2fcc 100644 --- a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java +++ b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java @@ -61,8 +61,20 @@ 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[] {}); - for (TransactionSignature signature : signatures) + for (TransactionSignature signature : signatures) { pushes.add(signature.encodeToBitcoin()); + } + 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[] {}); + for (TransactionSignature signature : signatures) { + pushes.add(signature.encodeToBitcoin()); + } + pushes.add(new byte[] {}); pushes.add(witnessScript.getProgram()); return TransactionWitness.of(pushes); } diff --git a/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java b/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java index 2ab47dcca..357dcb12d 100644 --- a/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java +++ b/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java @@ -84,6 +84,29 @@ public static Script createP2shErpRedeemScript( return erpRedeemScript; } + public static Script createP2shP2wshErpRedeemScript( + Script defaultFederationRedeemScript, + Script erpFederationRedeemScript, + Long csvValue + ) { + byte[] serializedCsvValue = Utils.signedLongToByteArrayLE(csvValue); + + ScriptBuilder scriptBuilder = new ScriptBuilder(); + + Script erpP2shP2wshRedeemScript = scriptBuilder + .op(ScriptOpCodes.OP_0NOTEQUAL) + .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(); + return erpP2shP2wshRedeemScript; + } + public static boolean isP2shErpFed(List chunks) { return RedeemScriptValidator.hasP2shErpRedeemScriptStructure(chunks); } From 1d1530381258125adcdf32cd680b1a44571460fa Mon Sep 17 00:00:00 2001 From: Marcos Irisarri <53787863+marcos-iov@users.noreply.github.com> Date: Thu, 3 Aug 2023 18:03:01 -0300 Subject: [PATCH 07/20] Update src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java --- src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java index 9267e2fcc..7cc422532 100644 --- a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java +++ b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java @@ -74,7 +74,7 @@ public static TransactionWitness createWitnessErpScript(Script witnessScript, Li for (TransactionSignature signature : signatures) { pushes.add(signature.encodeToBitcoin()); } - pushes.add(new byte[] {}); + pushes.add(new byte[] {}); // OP_NOTIF argument. If a 0 is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys pushes.add(witnessScript.getProgram()); return TransactionWitness.of(pushes); } From 38ec8fa9a14756c5ad16ba6273a6c8893d07b861 Mon Sep 17 00:00:00 2001 From: julia zack Date: Tue, 8 Aug 2023 16:14:34 -0300 Subject: [PATCH 08/20] Add witnessEmergencyScript, remove OP_0NOTEQUAL from redeem and refactor the byte pushed for op_notif argument --- .../co/rsk/bitcoinj/core/TransactionWitness.java | 13 ++++++++++++- .../script/P2shErpFederationRedeemScriptParser.java | 1 - 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java index 7cc422532..6a73fc0b3 100644 --- a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java +++ b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java @@ -74,7 +74,18 @@ public static TransactionWitness createWitnessErpScript(Script witnessScript, Li for (TransactionSignature signature : signatures) { pushes.add(signature.encodeToBitcoin()); } - pushes.add(new byte[] {}); // OP_NOTIF argument. If a 0 is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys + pushes.add(new byte[0]); // OP_NOTIF argument. If a 0 is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys + pushes.add(witnessScript.getProgram()); + return TransactionWitness.of(pushes); + } + + public static TransactionWitness createWitnessErpEmergencyScript(Script witnessScript, List signatures) { + List pushes = new ArrayList<>(signatures.size() + 3); + pushes.add(new byte[] {}); + for (TransactionSignature signature : signatures) { + pushes.add(signature.encodeToBitcoin()); + } + pushes.add(new byte[1]); // OP_NOTIF argument. If a 0 is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys pushes.add(witnessScript.getProgram()); return TransactionWitness.of(pushes); } diff --git a/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java b/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java index 357dcb12d..1a973846a 100644 --- a/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java +++ b/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java @@ -94,7 +94,6 @@ public static Script createP2shP2wshErpRedeemScript( ScriptBuilder scriptBuilder = new ScriptBuilder(); Script erpP2shP2wshRedeemScript = scriptBuilder - .op(ScriptOpCodes.OP_0NOTEQUAL) .op(ScriptOpCodes.OP_NOTIF) .addChunks(defaultFederationRedeemScript.getChunks()) .op(ScriptOpCodes.OP_ELSE) From fcdb7c2373e6f8a5917eb6528eedf1626b9520f1 Mon Sep 17 00:00:00 2001 From: julia zack Date: Fri, 11 Aug 2023 15:56:12 -0300 Subject: [PATCH 09/20] Correct OP_NOTIF argument --- src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java index 6a73fc0b3..c3c5cc4bd 100644 --- a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java +++ b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java @@ -74,7 +74,7 @@ public static TransactionWitness createWitnessErpScript(Script witnessScript, Li for (TransactionSignature signature : signatures) { pushes.add(signature.encodeToBitcoin()); } - pushes.add(new byte[0]); // OP_NOTIF argument. If a 0 is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys + pushes.add(new byte[] {}); // OP_NOTIF argument. If an empty vector is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys pushes.add(witnessScript.getProgram()); return TransactionWitness.of(pushes); } @@ -85,7 +85,7 @@ public static TransactionWitness createWitnessErpEmergencyScript(Script witnessS for (TransactionSignature signature : signatures) { pushes.add(signature.encodeToBitcoin()); } - pushes.add(new byte[1]); // OP_NOTIF argument. If a 0 is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys + pushes.add(new byte[] {1}); // OP_NOTIF argument. If an empty vector is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys pushes.add(witnessScript.getProgram()); return TransactionWitness.of(pushes); } From 25f6d8010e1b76809cc1e3b5af7f58957a30d89d Mon Sep 17 00:00:00 2001 From: Marcos Date: Fri, 11 Aug 2023 16:11:05 -0300 Subject: [PATCH 10/20] Add function to create a flyover p2wsh script --- .../P2shErpFederationRedeemScriptParser.java | 27 +++++++++++++++++++ .../co/rsk/bitcoinj/script/ScriptBuilder.java | 6 ++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java b/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java index 1a973846a..d31e71b78 100644 --- a/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java +++ b/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java @@ -1,5 +1,6 @@ package co.rsk.bitcoinj.script; +import co.rsk.bitcoinj.core.Sha256Hash; import co.rsk.bitcoinj.core.Utils; import co.rsk.bitcoinj.core.VerificationException; import org.slf4j.Logger; @@ -106,6 +107,32 @@ public static Script createP2shP2wshErpRedeemScript( return erpP2shP2wshRedeemScript; } + public static Script createP2shP2wshErpRedeemScriptWithFlyover( + Script defaultFederationRedeemScript, + Script erpFederationRedeemScript, + Sha256Hash derivationPath, + Long csvValue + ) { + byte[] serializedCsvValue = Utils.signedLongToByteArrayLE(csvValue); + + ScriptBuilder scriptBuilder = new ScriptBuilder(); + + Script erpP2shP2wshRedeemScript = scriptBuilder + .data(derivationPath.getBytes()) + .op(ScriptOpCodes.OP_DROP) + .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(); + + return erpP2shP2wshRedeemScript; + } + 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 a99d1d8bb..120516f29 100644 --- a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java +++ b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java @@ -271,13 +271,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() <= 16); // That's the max we can represent with a single opcode. 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(); } From 2390c0b552efe718447f23093b1047089fbc9218 Mon Sep 17 00:00:00 2001 From: julia zack Date: Fri, 18 Aug 2023 16:44:18 -0300 Subject: [PATCH 11/20] Modify Wallet class to calculate fee for segwit --- .../java/co/rsk/bitcoinj/wallet/Wallet.java | 68 ++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java index 9b439501b..aba531156 100644 --- a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java +++ b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java @@ -398,7 +398,7 @@ public String toString(boolean includePrivateKeys, boolean includeTransactions, Coin estimatedBalance = getBalance(BalanceType.ESTIMATED); Coin availableBalance = getBalance(BalanceType.AVAILABLE_SPENDABLE); builder.append("Wallet containing ").append(estimatedBalance.toFriendlyString()).append(" (spendable: ") - .append(availableBalance.toFriendlyString()).append(") in:\n"); + .append(availableBalance.toFriendlyString()).append(") in:\n"); if (!watchedScripts.isEmpty()) { builder.append("\nWatched scripts:\n"); for (Script script : watchedScripts) { @@ -576,7 +576,7 @@ public void completeTx(SendRequest req) throws InsufficientMoneyException { } log.info("Completing send tx with {} outputs totalling {} and a fee of {}/kB", req.tx.getOutputs().size(), - value.toFriendlyString(), req.feePerKb.toFriendlyString()); + value.toFriendlyString(), req.feePerKb.toFriendlyString()); // If any inputs have already been added, we don't need to get their value from wallet Coin totalInput = Coin.ZERO; @@ -611,9 +611,12 @@ public void completeTx(SendRequest req) throws InsufficientMoneyException { CoinSelection bestCoinSelection; TransactionOutput bestChangeOutput = null; List updatedOutputValues = null; + boolean isSegwit = false; + //req.tx.hasWitness(); + log.info("isSegwit? {}", isSegwit); if (!req.emptyWallet) { // This can throw InsufficientMoneyException. - FeeCalculation feeCalculation = calculateFee(req, value, originalInputs, req.ensureMinRequiredFee, candidates); + FeeCalculation feeCalculation = calculateFee(req, value, originalInputs, req.ensureMinRequiredFee, candidates, isSegwit); bestCoinSelection = feeCalculation.bestCoinSelection; bestChangeOutput = feeCalculation.bestChangeOutput; updatedOutputValues = feeCalculation.updatedOutputValues; @@ -914,7 +917,7 @@ private static class FeeCalculation { //region Fee calculation code public FeeCalculation calculateFee(SendRequest req, Coin value, List originalInputs, - boolean needAtLeastReferenceFee, List candidates) throws InsufficientMoneyException { + boolean needAtLeastReferenceFee, List candidates, boolean isSegwit) throws InsufficientMoneyException { FeeCalculation result; Coin fee = Coin.ZERO; while (true) { @@ -931,14 +934,14 @@ public FeeCalculation calculateFee(SendRequest req, Coin value, List originalInputs) { for (TransactionInput input : originalInputs) tx.addInput(new TransactionInput(params, tx, input.bitcoinSerialize())); From 8e048ea7db485de977faf6698ed5848fb15b3789 Mon Sep 17 00:00:00 2001 From: julia zack Date: Fri, 25 Aug 2023 13:00:32 -0300 Subject: [PATCH 12/20] Add isSegwit parameter to SendRequest. Correct calculations in Wallet class. Modify SIG_SIZE approx value. --- .../java/co/rsk/bitcoinj/script/Script.java | 2 +- .../co/rsk/bitcoinj/wallet/SendRequest.java | 2 + .../java/co/rsk/bitcoinj/wallet/Wallet.java | 66 ++++++++++++------- 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/script/Script.java b/src/main/java/co/rsk/bitcoinj/script/Script.java index dffab60cc..ff7aa764b 100644 --- a/src/main/java/co/rsk/bitcoinj/script/Script.java +++ b/src/main/java/co/rsk/bitcoinj/script/Script.java @@ -96,7 +96,7 @@ public enum VerifyFlag { private static final Logger log = LoggerFactory.getLogger(Script.class); public static final long MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes - public static final int SIG_SIZE = 75; + public static final int SIG_SIZE = 73; /** Max number of sigops allowed in a standard p2sh redeem script */ public static final int MAX_P2SH_SIGOPS = 15; diff --git a/src/main/java/co/rsk/bitcoinj/wallet/SendRequest.java b/src/main/java/co/rsk/bitcoinj/wallet/SendRequest.java index 400eaa89a..2ad9b07a6 100644 --- a/src/main/java/co/rsk/bitcoinj/wallet/SendRequest.java +++ b/src/main/java/co/rsk/bitcoinj/wallet/SendRequest.java @@ -60,6 +60,8 @@ public class SendRequest { */ public BtcTransaction tx; + public boolean isSegwit = false; + /** * When emptyWallet is set, all coins selected by the coin selector are sent to the first output in tx * (its value is ignored and set to {@link co.rsk.bitcoinj.wallet.Wallet#getBalance()} - the fees required diff --git a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java index aba531156..9c77407b0 100644 --- a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java +++ b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java @@ -40,6 +40,7 @@ import co.rsk.bitcoinj.script.*; import co.rsk.bitcoinj.signers.*; import org.slf4j.*; +import org.spongycastle.util.encoders.Hex; import javax.annotation.*; import java.util.*; @@ -571,6 +572,7 @@ public void completeTx(SendRequest req) throws InsufficientMoneyException { checkArgument(!req.completed, "Given SendRequest has already been completed."); // Calculate the amount of value we need to import. Coin value = Coin.ZERO; + for (TransactionOutput output : req.tx.getOutputs()) { value = value.add(output.getValue()); } @@ -611,12 +613,9 @@ public void completeTx(SendRequest req) throws InsufficientMoneyException { CoinSelection bestCoinSelection; TransactionOutput bestChangeOutput = null; List updatedOutputValues = null; - boolean isSegwit = false; - //req.tx.hasWitness(); - log.info("isSegwit? {}", isSegwit); if (!req.emptyWallet) { // This can throw InsufficientMoneyException. - FeeCalculation feeCalculation = calculateFee(req, value, originalInputs, req.ensureMinRequiredFee, candidates, isSegwit); + FeeCalculation feeCalculation = calculateFee(req, value, originalInputs, req.ensureMinRequiredFee, candidates); bestCoinSelection = feeCalculation.bestCoinSelection; bestChangeOutput = feeCalculation.bestChangeOutput; updatedOutputValues = feeCalculation.updatedOutputValues; @@ -917,7 +916,7 @@ private static class FeeCalculation { //region Fee calculation code public FeeCalculation calculateFee(SendRequest req, Coin value, List originalInputs, - boolean needAtLeastReferenceFee, List candidates, boolean isSegwit) throws InsufficientMoneyException { + boolean needAtLeastReferenceFee, List candidates) throws InsufficientMoneyException { FeeCalculation result; Coin fee = Coin.ZERO; while (true) { @@ -1000,18 +999,23 @@ public FeeCalculation calculateFee(SendRequest req, Coin value, List originalInputs) { for (TransactionInput input : originalInputs) tx.addInput(new TransactionInput(params, tx, input.bitcoinSerialize())); From 0e95d9e504315a4183965787d2f5cf8eada0a47b Mon Sep 17 00:00:00 2001 From: julia zack Date: Fri, 25 Aug 2023 13:05:01 -0300 Subject: [PATCH 13/20] Remove unnecessary code (prints and etc) --- src/main/java/co/rsk/bitcoinj/wallet/Wallet.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java index 9c77407b0..789097a01 100644 --- a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java +++ b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java @@ -40,7 +40,6 @@ import co.rsk.bitcoinj.script.*; import co.rsk.bitcoinj.signers.*; import org.slf4j.*; -import org.spongycastle.util.encoders.Hex; import javax.annotation.*; import java.util.*; @@ -572,7 +571,6 @@ public void completeTx(SendRequest req) throws InsufficientMoneyException { checkArgument(!req.completed, "Given SendRequest has already been completed."); // Calculate the amount of value we need to import. Coin value = Coin.ZERO; - for (TransactionOutput output : req.tx.getOutputs()) { value = value.add(output.getValue()); } @@ -1023,8 +1021,6 @@ public FeeCalculation calculateFee(SendRequest req, Coin value, List Date: Fri, 25 Aug 2023 20:21:20 -0300 Subject: [PATCH 14/20] Add bytesToAdd to all txs totalSize --- src/main/java/co/rsk/bitcoinj/wallet/Wallet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java index 789097a01..b45badeae 100644 --- a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java +++ b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java @@ -1002,9 +1002,9 @@ public FeeCalculation calculateFee(SendRequest req, Coin value, List Date: Mon, 28 Aug 2023 10:43:37 -0300 Subject: [PATCH 15/20] Add indentation --- src/main/java/co/rsk/bitcoinj/wallet/Wallet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java index b45badeae..e376803c0 100644 --- a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java +++ b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java @@ -931,14 +931,14 @@ public FeeCalculation calculateFee(SendRequest req, Coin value, List Date: Mon, 28 Aug 2023 10:45:14 -0300 Subject: [PATCH 16/20] Add indentation --- src/main/java/co/rsk/bitcoinj/wallet/Wallet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java index e376803c0..6fc7f2afa 100644 --- a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java +++ b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java @@ -398,7 +398,7 @@ public String toString(boolean includePrivateKeys, boolean includeTransactions, Coin estimatedBalance = getBalance(BalanceType.ESTIMATED); Coin availableBalance = getBalance(BalanceType.AVAILABLE_SPENDABLE); builder.append("Wallet containing ").append(estimatedBalance.toFriendlyString()).append(" (spendable: ") - .append(availableBalance.toFriendlyString()).append(") in:\n"); + .append(availableBalance.toFriendlyString()).append(") in:\n"); if (!watchedScripts.isEmpty()) { builder.append("\nWatched scripts:\n"); for (Script script : watchedScripts) { @@ -576,7 +576,7 @@ public void completeTx(SendRequest req) throws InsufficientMoneyException { } log.info("Completing send tx with {} outputs totalling {} and a fee of {}/kB", req.tx.getOutputs().size(), - value.toFriendlyString(), req.feePerKb.toFriendlyString()); + value.toFriendlyString(), req.feePerKb.toFriendlyString()); // If any inputs have already been added, we don't need to get their value from wallet Coin totalInput = Coin.ZERO; From 0e2a246ad7e6cfa9a9b1702634e2ae7f8febc20b Mon Sep 17 00:00:00 2001 From: julia zack Date: Mon, 28 Aug 2023 18:23:47 -0300 Subject: [PATCH 17/20] Refactor. Modify size and fee calculation --- .../rsk/bitcoinj/core/TransactionWitness.java | 46 +++- .../P2shErpFederationRedeemScriptParser.java | 49 ---- ...wshErpFederationNewRedeemScriptParser.java | 28 ++ ...hP2wshErpFederationRedeemScriptParser.java | 54 ++++ .../java/co/rsk/bitcoinj/script/Script.java | 3 +- .../co/rsk/bitcoinj/script/ScriptBuilder.java | 29 ++ .../java/co/rsk/bitcoinj/wallet/Wallet.java | 16 +- .../bitcoinj/script/P2shP2wshScriptTest.java | 38 --- .../witness/P2shP2wshNewRedeemScriptTest.java | 194 +++++++++++++ .../bitcoinj/witness/P2shP2wshScriptTest.java | 260 ++++++++++++++++++ .../witness/utils/BitcoinTestUtils.java | 25 ++ 11 files changed, 636 insertions(+), 106 deletions(-) create mode 100644 src/main/java/co/rsk/bitcoinj/script/P2shP2wshErpFederationNewRedeemScriptParser.java create mode 100644 src/main/java/co/rsk/bitcoinj/script/P2shP2wshErpFederationRedeemScriptParser.java delete mode 100644 src/test/java/co/rsk/bitcoinj/script/P2shP2wshScriptTest.java create mode 100644 src/test/java/co/rsk/bitcoinj/witness/P2shP2wshNewRedeemScriptTest.java create mode 100644 src/test/java/co/rsk/bitcoinj/witness/P2shP2wshScriptTest.java create mode 100644 src/test/java/co/rsk/bitcoinj/witness/utils/BitcoinTestUtils.java diff --git a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java index c3c5cc4bd..838f76acf 100644 --- a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java +++ b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java @@ -58,34 +58,60 @@ public static TransactionWitness createWitness(@Nullable final TransactionSignat return witness; } - public static TransactionWitness createWitnessScript(Script witnessScript, List signatures) { - List pushes = new ArrayList<>(signatures.size() + 2); + public static TransactionWitness createWitnessErpStandardScript(Script witnessScript, List signatures) { + List pushes = new ArrayList<>(signatures.size() + 3); pushes.add(new byte[] {}); for (TransactionSignature signature : signatures) { pushes.add(signature.encodeToBitcoin()); } + pushes.add(new byte[] {}); // OP_NOTIF argument. If an empty vector is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys pushes.add(witnessScript.getProgram()); return TransactionWitness.of(pushes); } - public static TransactionWitness createWitnessErpScript(Script witnessScript, List signatures) { + public static TransactionWitness createWitnessErpEmergencyScript(Script witnessScript, List signatures) { List pushes = new ArrayList<>(signatures.size() + 3); pushes.add(new byte[] {}); for (TransactionSignature signature : signatures) { pushes.add(signature.encodeToBitcoin()); } - pushes.add(new byte[] {}); // OP_NOTIF argument. If an empty vector is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys + pushes.add(new byte[] {1}); // OP_NOTIF argument. If an empty vector is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys pushes.add(witnessScript.getProgram()); return TransactionWitness.of(pushes); } - public static TransactionWitness createWitnessErpEmergencyScript(Script witnessScript, List signatures) { - List pushes = new ArrayList<>(signatures.size() + 3); - pushes.add(new byte[] {}); - for (TransactionSignature signature : signatures) { - pushes.add(signature.encodeToBitcoin()); + public static TransactionWitness createWitnessErpStandardNewScript(Script witnessScript, List thresholdSignatures, int signaturesSize) { + int zeroSignaturesSize = signaturesSize - thresholdSignatures.size(); + List pushes = new ArrayList<>(signaturesSize + 2); + + // signatures to be used + for (int i = 0; i < thresholdSignatures.size(); i++) { + pushes.add(thresholdSignatures.get(i).encodeToBitcoin()); } - pushes.add(new byte[] {1}); // OP_NOTIF argument. If an empty vector is set it will validate against the standard keys, if a 1 is set it will validate against the emergency keys + + // empty signatures + 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 createWitnessErpEmergencyNewScript(Script witnessScript, List thresholdSignatures, int signaturesSize) { + int zeroSignaturesSize = signaturesSize - thresholdSignatures.size(); + List pushes = new ArrayList<>(signaturesSize + 2); + + // signatures to be used + for (int i = 0; i < thresholdSignatures.size(); i++) { + pushes.add(thresholdSignatures.get(i).encodeToBitcoin()); + } + + // empty signatures + 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); } diff --git a/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java b/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java index d31e71b78..2ab47dcca 100644 --- a/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java +++ b/src/main/java/co/rsk/bitcoinj/script/P2shErpFederationRedeemScriptParser.java @@ -1,6 +1,5 @@ package co.rsk.bitcoinj.script; -import co.rsk.bitcoinj.core.Sha256Hash; import co.rsk.bitcoinj.core.Utils; import co.rsk.bitcoinj.core.VerificationException; import org.slf4j.Logger; @@ -85,54 +84,6 @@ public static Script createP2shErpRedeemScript( return erpRedeemScript; } - public static Script createP2shP2wshErpRedeemScript( - Script defaultFederationRedeemScript, - Script erpFederationRedeemScript, - Long csvValue - ) { - byte[] serializedCsvValue = Utils.signedLongToByteArrayLE(csvValue); - - ScriptBuilder scriptBuilder = new ScriptBuilder(); - - Script erpP2shP2wshRedeemScript = 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(); - return erpP2shP2wshRedeemScript; - } - - public static Script createP2shP2wshErpRedeemScriptWithFlyover( - Script defaultFederationRedeemScript, - Script erpFederationRedeemScript, - Sha256Hash derivationPath, - Long csvValue - ) { - byte[] serializedCsvValue = Utils.signedLongToByteArrayLE(csvValue); - - ScriptBuilder scriptBuilder = new ScriptBuilder(); - - Script erpP2shP2wshRedeemScript = scriptBuilder - .data(derivationPath.getBytes()) - .op(ScriptOpCodes.OP_DROP) - .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(); - - return erpP2shP2wshRedeemScript; - } - public static boolean isP2shErpFed(List chunks) { return RedeemScriptValidator.hasP2shErpRedeemScriptStructure(chunks); } diff --git a/src/main/java/co/rsk/bitcoinj/script/P2shP2wshErpFederationNewRedeemScriptParser.java b/src/main/java/co/rsk/bitcoinj/script/P2shP2wshErpFederationNewRedeemScriptParser.java new file mode 100644 index 000000000..37aa0e57d --- /dev/null +++ b/src/main/java/co/rsk/bitcoinj/script/P2shP2wshErpFederationNewRedeemScriptParser.java @@ -0,0 +1,28 @@ +package co.rsk.bitcoinj.script; + +import co.rsk.bitcoinj.core.Utils; + +public class P2shP2wshErpFederationNewRedeemScriptParser { + public static Script createErpP2shP2wshNewRedeemScript( + Script defaultFederationRedeemScript, + Script erpFederationRedeemScript, + Long 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(); + + return erpRedeemScript; + } +} diff --git a/src/main/java/co/rsk/bitcoinj/script/P2shP2wshErpFederationRedeemScriptParser.java b/src/main/java/co/rsk/bitcoinj/script/P2shP2wshErpFederationRedeemScriptParser.java new file mode 100644 index 000000000..d2eaef85a --- /dev/null +++ b/src/main/java/co/rsk/bitcoinj/script/P2shP2wshErpFederationRedeemScriptParser.java @@ -0,0 +1,54 @@ +package co.rsk.bitcoinj.script; + +import co.rsk.bitcoinj.core.Sha256Hash; +import co.rsk.bitcoinj.core.Utils; + +public class P2shP2wshErpFederationRedeemScriptParser { + public static Script createP2shP2wshErpRedeemScript( + Script defaultFederationRedeemScript, + Script erpFederationRedeemScript, + Long csvValue + ) { + byte[] serializedCsvValue = Utils.signedLongToByteArrayLE(csvValue); + + ScriptBuilder scriptBuilder = new ScriptBuilder(); + + Script erpP2shP2wshRedeemScript = 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(); + return erpP2shP2wshRedeemScript; + } + + public static Script createP2shP2wshErpRedeemScriptWithFlyover( + Script defaultFederationRedeemScript, + Script erpFederationRedeemScript, + Sha256Hash derivationPath, + Long csvValue + ) { + byte[] serializedCsvValue = Utils.signedLongToByteArrayLE(csvValue); + + ScriptBuilder scriptBuilder = new ScriptBuilder(); + + Script erpP2shP2wshRedeemScript = scriptBuilder + .data(derivationPath.getBytes()) + .op(ScriptOpCodes.OP_DROP) + .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(); + + return erpP2shP2wshRedeemScript; + } +} diff --git a/src/main/java/co/rsk/bitcoinj/script/Script.java b/src/main/java/co/rsk/bitcoinj/script/Script.java index ff7aa764b..9adab9610 100644 --- a/src/main/java/co/rsk/bitcoinj/script/Script.java +++ b/src/main/java/co/rsk/bitcoinj/script/Script.java @@ -610,7 +610,8 @@ public int getNumberOfBytesRequiredToSpend(@Nullable BtcECKey pubKey, @Nullable if (isPayToScriptHash()) { // scriptSig: [sig] [sig...] checkArgument(redeemScript != null, "P2SH script requires redeemScript to be spent"); - return redeemScript.getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + redeemScript.getProgram().length; + System.out.println(redeemScript.getProgram().length); + return redeemScript.getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + redeemScript.getProgram().length + 1 + 1; // 1 byte for first empty byte, 1 byte for op_notif argument } else if (isSentToMultiSig()) { // scriptSig: OP_0 [sig] [sig...] return getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + 1; diff --git a/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java b/src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java index 120516f29..509d86f72 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,16 @@ public static Script createRedeemScript(int threshold, List pubkeys) { return ScriptBuilder.createMultiSigOutputScript(threshold, pubkeys); } + /** + * Creates redeem script with new structure with given public keys and threshold. Given public keys will be placed in + * redeem script in the lexicographical sorting order. + */ + 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/main/java/co/rsk/bitcoinj/wallet/Wallet.java b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java index 6fc7f2afa..cf733340a 100644 --- a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java +++ b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java @@ -40,6 +40,7 @@ import co.rsk.bitcoinj.script.*; import co.rsk.bitcoinj.signers.*; import org.slf4j.*; +import org.spongycastle.util.encoders.Hex; import javax.annotation.*; import java.util.*; @@ -1038,8 +1039,11 @@ public static int calculateTxBaseSize(BtcTransaction spendTx, boolean isSegwit) for (int i = 0; i < inputsQuantity; i++) { byte[] input = spendTx.getInput(i).bitcoinSerialize(); baseSize += input.length; - // at this time the scriptSig for every input is empty = 00. so we should count its bytes manually and remove the byte from the empty one. - baseSize += 36 - 1; + + baseSize += 4; // redeem script size in hexa - is not always 4 but approximately. + if (isSegwit) { + baseSize += 36 - 1; // at this time the scriptSig for every input is empty = 00. so we should count its bytes manually and remove the byte from the empty one. + } } int outputsQuantity = spendTx.getOutputs().size(); @@ -1048,9 +1052,6 @@ public static int calculateTxBaseSize(BtcTransaction spendTx, boolean isSegwit) baseSize += output.length; } - baseSize += 4; // version size - baseSize += 4; // locktime size - if (isSegwit) { baseSize += 1; // marker size baseSize += 1; // flag size @@ -1077,11 +1078,10 @@ public static int calculateWitnessTxVirtualSize(int txBaseSize, int txTotalSize) public static int bytesToAdd(BtcTransaction tx) { int bytes = 0; + bytes += 4; // version size bytes += 1; // inputs quantity bytes size bytes += 1; // outputs quantity bytes size - bytes += 1; // empty vector before signatures bytes size - bytes += 1; // op_notif value bytes size - bytes += 4; // 3 or 4 bytes before the redeem are added. count them like 4 + bytes += 4; // locktime size return bytes; } diff --git a/src/test/java/co/rsk/bitcoinj/script/P2shP2wshScriptTest.java b/src/test/java/co/rsk/bitcoinj/script/P2shP2wshScriptTest.java deleted file mode 100644 index 5510e0d11..000000000 --- a/src/test/java/co/rsk/bitcoinj/script/P2shP2wshScriptTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package co.rsk.bitcoinj.script; - -import co.rsk.bitcoinj.core.Address; -import co.rsk.bitcoinj.core.BtcECKey; -import co.rsk.bitcoinj.core.NetworkParameters; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import org.junit.Assert; -import org.junit.Test; -import org.spongycastle.util.encoders.Hex; - -public class P2shP2wshScriptTest { - - @Test - public void getAddressFromP2shP2wshScript() { - List keys = Arrays.asList(new String[]{ - "027de2af71862e0c64bf0ec5a66e3abc3b01fc57877802e6a6a81f6ea1d3561007", - "02d9c67fef9f8d0707cbcca195eb5f26c6a65da6ca2d6130645c434bb924063856", - "0346f033b8652a17d319d3ecbbbf20fd2cd663a6548173b9419d8228eef095012e" - }).stream().map(k -> BtcECKey.fromPublicOnly(Hex.decode(k))).collect(Collectors.toList()); - - Script redeemScript = new ScriptBuilder().createRedeemScript( - keys.size() / 2 + 1, - keys - ); - - Script p2shP2wshOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(redeemScript); - Address segwitAddress = Address.fromP2SHScript( - NetworkParameters.fromID(NetworkParameters.ID_TESTNET), - p2shP2wshOutputScript - ); - - Assert.assertEquals("2NCQHJJuG2iQjN2Be3QYXwWvFgS6AZ4MEkL", segwitAddress.toBase58()); - // https://mempool.space/testnet/tx/1744459aeaf7369aadc9fc40de9ab2bf575b14e35029b35a7ee4bbd3de65af7f - } - -} diff --git a/src/test/java/co/rsk/bitcoinj/witness/P2shP2wshNewRedeemScriptTest.java b/src/test/java/co/rsk/bitcoinj/witness/P2shP2wshNewRedeemScriptTest.java new file mode 100644 index 000000000..23cf3b15e --- /dev/null +++ b/src/test/java/co/rsk/bitcoinj/witness/P2shP2wshNewRedeemScriptTest.java @@ -0,0 +1,194 @@ + +package co.rsk.bitcoinj.witness; + +import co.rsk.bitcoinj.core.*; +import co.rsk.bitcoinj.crypto.TransactionSignature; +import co.rsk.bitcoinj.script.P2shP2wshErpFederationNewRedeemScriptParser; +import co.rsk.bitcoinj.script.Script; +import co.rsk.bitcoinj.script.ScriptBuilder; +import co.rsk.bitcoinj.witness.utils.BitcoinTestUtils; +import org.junit.Test; +import org.spongycastle.util.encoders.Hex; + +import java.util.ArrayList; +import java.util.List; + +import static co.rsk.bitcoinj.script.ScriptOpCodes.OP_0; + +public class P2shP2wshNewRedeemScriptTest { + + @Test + void spendFromP2shP2wshNewRedeemErpFedStandard() { + + NetworkParameters networkParameters = NetworkParameters.fromID(NetworkParameters.ID_TESTNET); // testnet + long activationDelay = 30; + + String[] seeds = new String[62]; + for (int i = 0; i < 62; i++ ) { + int j = i + 1; + seeds[i] = ("fed" + j); + } + List standardKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + seeds, + true + ); + + String[] emergencySeeds = new String[4]; + for (int i = 0; i < 4; i++ ) { + int j = i + 1; + emergencySeeds[i] = ("erp" + j); + } + List emergencyKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + emergencySeeds, + true + ); + + Script standardRedeemScript = new ScriptBuilder().createNewRedeemScript(standardKeys.size() / 2 + 1, standardKeys); + Script emergencyRedeemScript = new ScriptBuilder().createNewRedeemScript(emergencyKeys.size() / 2 + 1, emergencyKeys); + Script redeemScript = P2shP2wshErpFederationNewRedeemScriptParser.createErpP2shP2wshNewRedeemScript(standardRedeemScript, emergencyRedeemScript, activationDelay); + + Script p2shP2wshOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(redeemScript); + Address segwitAddress = Address.fromP2SHScript( + networkParameters, + p2shP2wshOutputScript + ); + System.out.println(segwitAddress); + + Sha256Hash fundTxHash = Sha256Hash.wrap("3661d179d4dfce84279dafd66d44f1dd8a002b24994e9f58902934806da01e5c"); + int outputIndex = 0; + //Address destinationAddress = Address.fromBase58(networkParameters, "12MXsCtte9onzqaHwN5VcnwZKGd7oDSsQq"); // mainnet + Address destinationAddress = Address.fromBase58(networkParameters, "msgc5Gtz2L9MVhXPDrFRCYPa16QgoZ2EjP"); // testnet + + Coin value = Coin.valueOf(10_000); + Coin fee = Coin.valueOf(2_000); + + BtcTransaction spendTx = new BtcTransaction(networkParameters); + spendTx.addInput(fundTxHash, outputIndex, new Script(new byte[]{})); + spendTx.addOutput(value.minus(fee), destinationAddress); + spendTx.setVersion(2); + + byte[] redeemScriptHash = Sha256Hash.hash(redeemScript.getProgram()); + Script scriptSig = new ScriptBuilder().number(OP_0).data(redeemScriptHash).build(); + Script segwitScriptSig = new ScriptBuilder().data(scriptSig.getProgram()).build(); + spendTx.getInput(0).setScriptSig(segwitScriptSig); + + // Create signatures + int inputIndex = 0; + Sha256Hash sigHash = spendTx.hashForWitnessSignature( + inputIndex, + redeemScript, + value, + BtcTransaction.SigHash.ALL, + false + ); + + int thresholdSignaturesSize = standardKeys.size() / 2 + 1; + List thresholdSignatures = new ArrayList<>(); + + for (int i = 0; i < thresholdSignaturesSize; i++) { + BtcECKey keyToSign = standardKeys.get(i); + BtcECKey.ECDSASignature signature = keyToSign.sign(sigHash); + TransactionSignature txSignature = new TransactionSignature( + signature, + BtcTransaction.SigHash.ALL, + false + ); + thresholdSignatures.add(txSignature); + } + + TransactionWitness txWitness = TransactionWitness.createWitnessErpStandardNewScript(redeemScript, thresholdSignatures, standardKeys.size()); + spendTx.setWitness(inputIndex, txWitness); + + + // Uncomment to print the raw tx in console and broadcast https://blockstream.info/testnet/tx/push + System.out.println(Hex.toHexString(spendTx.bitcoinSerialize())); + } + + @Test + void spendFromP2shP2wshNewRedeemErpFedEmergency() { + + NetworkParameters networkParameters = NetworkParameters.fromID(NetworkParameters.ID_TESTNET); // testnet + long activationDelay = 30; + + String[] seeds = new String[60]; + for (int i = 0; i < 60; i++ ) { + int j = i + 1; + seeds[i] = ("fed" + j); + } + List standardKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + seeds, + true + ); + + String[] emergencySeeds = new String[4]; + for (int i = 0; i < 4; i++ ) { + int j = i + 1; + emergencySeeds[i] = ("erp" + j); + } + List emergencyKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + emergencySeeds, + true + ); + + Script standardRedeemScript = new ScriptBuilder().createNewRedeemScript(standardKeys.size() / 2 + 1, standardKeys); + Script emergencyRedeemScript = new ScriptBuilder().createNewRedeemScript(emergencyKeys.size() / 2 + 1, emergencyKeys); + Script redeemScript = P2shP2wshErpFederationNewRedeemScriptParser.createErpP2shP2wshNewRedeemScript(standardRedeemScript, emergencyRedeemScript, activationDelay); + + Script p2shP2wshOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(redeemScript); + Address segwitAddress = Address.fromP2SHScript( + networkParameters, + p2shP2wshOutputScript + ); + System.out.println(segwitAddress); + + Sha256Hash fundTxHash = Sha256Hash.wrap("84538a84c4026e3887f3232fad35114603aa7a18530ae4b3083471f846d252c8"); + int outputIndex = 0; + //Address destinationAddress = Address.fromBase58(networkParameters, "12MXsCtte9onzqaHwN5VcnwZKGd7oDSsQq"); // mainnet + Address destinationAddress = Address.fromBase58(networkParameters, "msgc5Gtz2L9MVhXPDrFRCYPa16QgoZ2EjP"); // testnet + + Coin value = Coin.valueOf(10_000); + Coin fee = Coin.valueOf(2_000); + + BtcTransaction spendTx = new BtcTransaction(networkParameters); + spendTx.addInput(fundTxHash, outputIndex, new Script(new byte[]{})); + spendTx.getInput(0).setSequenceNumber(activationDelay); + spendTx.addOutput(value.minus(fee), destinationAddress); + spendTx.setVersion(2); + + byte[] redeemScriptHash = Sha256Hash.hash(redeemScript.getProgram()); + Script scriptSig = new ScriptBuilder().number(OP_0).data(redeemScriptHash).build(); + Script segwitScriptSig = new ScriptBuilder().data(scriptSig.getProgram()).build(); + spendTx.getInput(0).setScriptSig(segwitScriptSig); + + // Create signatures + int inputIndex = 0; + Sha256Hash sigHash = spendTx.hashForWitnessSignature( + inputIndex, + redeemScript, + value, + BtcTransaction.SigHash.ALL, + false + ); + + int thresholdSignaturesSize = emergencyKeys.size() / 2 + 1; + List thresholdSignatures = new ArrayList<>(); + + for (int i = 0; i < thresholdSignaturesSize; i++) { + BtcECKey keyToSign = emergencyKeys.get(i); + BtcECKey.ECDSASignature signature = keyToSign.sign(sigHash); + TransactionSignature txSignature = new TransactionSignature( + signature, + BtcTransaction.SigHash.ALL, + false + ); + thresholdSignatures.add(txSignature); + } + + TransactionWitness txWitness = TransactionWitness.createWitnessErpEmergencyNewScript(redeemScript, thresholdSignatures, emergencyKeys.size()); + spendTx.setWitness(inputIndex, txWitness); + + // Uncomment to print the raw tx in console and broadcast https://blockstream.info/testnet/tx/push + System.out.println(Hex.toHexString(spendTx.bitcoinSerialize())); + } +} + diff --git a/src/test/java/co/rsk/bitcoinj/witness/P2shP2wshScriptTest.java b/src/test/java/co/rsk/bitcoinj/witness/P2shP2wshScriptTest.java new file mode 100644 index 000000000..47504c7eb --- /dev/null +++ b/src/test/java/co/rsk/bitcoinj/witness/P2shP2wshScriptTest.java @@ -0,0 +1,260 @@ +package co.rsk.bitcoinj.witness; + +import co.rsk.bitcoinj.core.*; + +import java.util.ArrayList; +import java.util.List; + +import co.rsk.bitcoinj.crypto.TransactionSignature; +import co.rsk.bitcoinj.script.P2shP2wshErpFederationRedeemScriptParser; +import co.rsk.bitcoinj.script.Script; +import co.rsk.bitcoinj.script.ScriptBuilder; +import co.rsk.bitcoinj.witness.utils.BitcoinTestUtils; +import org.junit.Test; +import org.spongycastle.util.encoders.Hex; + +import static co.rsk.bitcoinj.script.ScriptOpCodes.OP_0; + +public class P2shP2wshScriptTest { + + @Test + void spendFromP2shP2wshErpFedStandard() { + NetworkParameters networkParameters = NetworkParameters.fromID(NetworkParameters.ID_TESTNET); + long activationDelay = 30; + + List standardKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[]{ + "fed1", "fed2", "fed3", "fed4", "fed5", "fed6", "fed7", "fed8", "fed9", "fed10", + "fed11", "fed12", "fed13", "fed14", "fed15", "fed16", "fed17", "fed18", "fed19", "fed20" + }, + true + ); + + List emergencyKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[]{ + "erp1", "erp2", "erp3", "erp4","erp5", "erp6", "erp7", "erp8","erp9", "erp10", + "erp11", "erp12", "erp13", "erp14","erp15", "erp16", "erp17", "erp18","erp19", "erp20" + }, + true + ); + + Script standardRedeem = new ScriptBuilder().createRedeemScript(standardKeys.size()/2+1, standardKeys); + Script emergencyRedeem = new ScriptBuilder().createRedeemScript(emergencyKeys.size()/2+1, emergencyKeys); + Script redeemScript = P2shP2wshErpFederationRedeemScriptParser.createP2shP2wshErpRedeemScript(standardRedeem, emergencyRedeem, activationDelay); + + Script p2shP2wshOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(redeemScript); + Address segwitAddress = Address.fromP2SHScript( + networkParameters, + p2shP2wshOutputScript + ); + System.out.println(segwitAddress); + + Sha256Hash fundTxHash = Sha256Hash.wrap("214bed3040e1432bf23b6126a7e8ffc83ba7da4d54fe899ee12510f878444ea1"); + Coin value = Coin.valueOf(10_000); + int outputIndex = 0; + Coin fee = Coin.valueOf(1_000); + Address receiver = Address.fromBase58(networkParameters,"msgc5Gtz2L9MVhXPDrFRCYPa16QgoZ2EjP"); + + BtcTransaction spendTx = new BtcTransaction(networkParameters); + spendTx.addInput(fundTxHash, outputIndex, new Script(new byte[]{})); + spendTx.addOutput(value.minus(fee), receiver); + spendTx.setVersion(2); + + // Create signatures + int inputIndex = 0; + Sha256Hash sigHash = spendTx.hashForWitnessSignature( + inputIndex, + redeemScript, + value, + BtcTransaction.SigHash.ALL, + false + ); + + byte[] redeemScriptHash = Sha256Hash.hash(redeemScript.getProgram()); + Script scriptSig = new ScriptBuilder().number(OP_0).data(redeemScriptHash).build(); + Script segwitScriptSig = new ScriptBuilder().data(scriptSig.getProgram()).build(); + spendTx.getInput(0).setScriptSig(segwitScriptSig); + + int requiredSignatures = standardKeys.size() / 2 + 1; + List signatures = new ArrayList<>(); + + for (int i = 0; i < requiredSignatures; i++) { + BtcECKey keyToSign = standardKeys.get(i); + BtcECKey.ECDSASignature signature = keyToSign.sign(sigHash); + TransactionSignature txSignature = new TransactionSignature( + signature, + BtcTransaction.SigHash.ALL, + false + ); + signatures.add(txSignature); + } + + TransactionWitness txWitness = TransactionWitness.createWitnessErpStandardScript(redeemScript, signatures); + spendTx.setWitness(inputIndex, txWitness); + + // Uncomment to print the raw tx in console and broadcast https://blockstream.info/testnet/tx/push + System.out.println(Hex.toHexString(spendTx.bitcoinSerialize())); + } + + @Test + void spendFromP2shP2wshErpFedEmergency() { + NetworkParameters networkParameters = NetworkParameters.fromID(NetworkParameters.ID_TESTNET); + long activationDelay = 30; + + List standardKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[]{ + "fed1", "fed2", "fed3", "fed4", "fed5", "fed6", "fed7", "fed8", "fed9", "fed10", + "fed11", "fed12", "fed13", "fed14", "fed15", "fed16", "fed17", "fed18", "fed19", "fed20" + }, + true + ); + + List emergencyKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[]{ + "erp1", "erp2", "erp3", "erp4","erp5", "erp6", "erp7", "erp8","erp9", "erp10", + "erp11", "erp12", "erp13", "erp14","erp15", "erp16", "erp17", "erp18","erp19", "erp20" + }, + true + ); + + Script standardRedeem = new ScriptBuilder().createRedeemScript(standardKeys.size()/2+1, standardKeys); + Script emergencyRedeem = new ScriptBuilder().createRedeemScript(emergencyKeys.size()/2+1, emergencyKeys); + Script redeemScript = P2shP2wshErpFederationRedeemScriptParser.createP2shP2wshErpRedeemScript(standardRedeem, emergencyRedeem, activationDelay); + + Script p2shP2wshOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(redeemScript); + Address segwitAddress = Address.fromP2SHScript( + networkParameters, + p2shP2wshOutputScript + ); + System.out.println(segwitAddress); + + Sha256Hash fundTxHash = Sha256Hash.wrap(""); + Coin value = Coin.valueOf(10_000); + int outputIndex = 0; + Coin fee = Coin.valueOf(1_000); + Address receiver = Address.fromBase58(networkParameters,"msgc5Gtz2L9MVhXPDrFRCYPa16QgoZ2EjP"); + + BtcTransaction spendTx = new BtcTransaction(networkParameters); + spendTx.addInput(fundTxHash, outputIndex, new Script(new byte[]{})); + spendTx.getInput(0).setSequenceNumber(activationDelay); + spendTx.addOutput(value.minus(fee), receiver); + spendTx.setVersion(2); + + // Create signatures + int inputIndex = 0; + Sha256Hash sigHash = spendTx.hashForWitnessSignature( + inputIndex, + redeemScript, + value, + BtcTransaction.SigHash.ALL, + false + ); + + byte[] redeemScriptHash = Sha256Hash.hash(redeemScript.getProgram()); + Script scriptSig = new ScriptBuilder().number(OP_0).data(redeemScriptHash).build(); + Script segwitScriptSig = new ScriptBuilder().data(scriptSig.getProgram()).build(); + spendTx.getInput(0).setScriptSig(segwitScriptSig); + + int requiredSignatures = emergencyKeys.size() / 2 + 1; + List signatures = new ArrayList<>(); + + for (int i = 0; i < requiredSignatures; i++) { + BtcECKey keyToSign = emergencyKeys.get(i); + BtcECKey.ECDSASignature signature = keyToSign.sign(sigHash); + TransactionSignature txSignature = new TransactionSignature( + signature, + BtcTransaction.SigHash.ALL, + false + ); + signatures.add(txSignature); + } + + TransactionWitness txWitness = TransactionWitness.createWitnessErpEmergencyScript(redeemScript, signatures); + spendTx.setWitness(inputIndex, txWitness); + + // Uncomment to print the raw tx in console and broadcast https://blockstream.info/testnet/tx/push + System.out.println(Hex.toHexString(spendTx.bitcoinSerialize())); + } + + @Test + void spendFromP2shP2wshErpFedStandardWithFlyover() throws Exception { + NetworkParameters networkParameters = NetworkParameters.fromID(NetworkParameters.ID_TESTNET); + long activationDelay = 30; + + List standardKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[]{ + "fed1", "fed2", "fed3", "fed4", "fed5", "fed6", "fed7", "fed8", "fed9", "fed10", + "fed11", "fed12", "fed13", "fed14", "fed15", "fed16", "fed17", "fed18", "fed19", "fed20" + }, + true + ); + + List emergencyKeys = BitcoinTestUtils.getBtcEcKeysFromSeeds( + new String[]{ + "erp1", "erp2", "erp3", "erp4", "erp5", "erp6", "erp7", "erp8", "erp9", "erp10", + "erp11", "erp12", "erp13", "erp14", "erp15", "erp16", "erp17", "erp18", "erp19", "erp20" + }, + true + ); + + Sha256Hash flyoverDerivationPath = Sha256Hash.wrap("0100000000000000000000000000000000000000000000000000000000000000"); + + Script standardRedeem = new ScriptBuilder().createRedeemScript(standardKeys.size()/2+1, standardKeys); + Script emergencyRedeem = new ScriptBuilder().createRedeemScript(emergencyKeys.size()/2+1, emergencyKeys); + Script redeemScript = P2shP2wshErpFederationRedeemScriptParser.createP2shP2wshErpRedeemScriptWithFlyover(standardRedeem, emergencyRedeem, flyoverDerivationPath, activationDelay); + + Script p2shP2wshOutputScript = ScriptBuilder.createP2SHP2WSHOutputScript(redeemScript); + Address segwitAddress = Address.fromP2SHScript( + networkParameters, + p2shP2wshOutputScript + ); + System.out.println(segwitAddress); + + Sha256Hash fundTxHash = Sha256Hash.wrap("ca9c3ff2685d65a0adf571a30d77c6d12857af9edfbf8f3a19ca4c1fc1eb47f5"); + Coin value = Coin.valueOf(10_000); + int outputIndex = 0; + Coin fee = Coin.valueOf(1_000); + Address receiver = Address.fromBase58(networkParameters,"msgc5Gtz2L9MVhXPDrFRCYPa16QgoZ2EjP"); + + BtcTransaction spendTx = new BtcTransaction(networkParameters); + spendTx.addInput(fundTxHash, outputIndex, new Script(new byte[]{})); + spendTx.addOutput(value.minus(fee), receiver); + spendTx.setVersion(2); + + // Create signatures + int inputIndex = 0; + Sha256Hash sigHash = spendTx.hashForWitnessSignature( + inputIndex, + redeemScript, + value, + BtcTransaction.SigHash.ALL, + false + ); + + byte[] redeemScriptHash = Sha256Hash.hash(redeemScript.getProgram()); + Script scriptSig = new ScriptBuilder().number(OP_0).data(redeemScriptHash).build(); + Script segwitScriptSig = new ScriptBuilder().data(scriptSig.getProgram()).build(); + spendTx.getInput(0).setScriptSig(segwitScriptSig); + + int requiredSignatures = standardKeys.size() / 2 + 1; + List signatures = new ArrayList<>(); + + for (int i = 0; i < requiredSignatures; i++) { + BtcECKey keyToSign = standardKeys.get(i); + BtcECKey.ECDSASignature signature = keyToSign.sign(sigHash); + TransactionSignature txSignature = new TransactionSignature( + signature, + BtcTransaction.SigHash.ALL, + false + ); + signatures.add(txSignature); + } + + TransactionWitness txWitness = TransactionWitness.createWitnessErpStandardScript(redeemScript, signatures); + spendTx.setWitness(inputIndex, txWitness); + + // Uncomment to print the raw tx in console and broadcast https://blockstream.info/testnet/tx/push + System.out.println(Hex.toHexString(spendTx.bitcoinSerialize())); + } + +} diff --git a/src/test/java/co/rsk/bitcoinj/witness/utils/BitcoinTestUtils.java b/src/test/java/co/rsk/bitcoinj/witness/utils/BitcoinTestUtils.java new file mode 100644 index 000000000..45770ad48 --- /dev/null +++ b/src/test/java/co/rsk/bitcoinj/witness/utils/BitcoinTestUtils.java @@ -0,0 +1,25 @@ +package co.rsk.bitcoinj.witness.utils; + +import co.rsk.bitcoinj.core.BtcECKey; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import co.rsk.bitcoinj.core.Sha256Hash; + +public class BitcoinTestUtils { + + public static List getBtcEcKeysFromSeeds(String[] seeds, boolean sorted) { + List keys = Arrays + .stream(seeds) + .map(seed -> BtcECKey.fromPrivate(Sha256Hash.hash(seed.getBytes(StandardCharsets.UTF_8)))) + .collect(Collectors.toList()); + + if (sorted) { + keys.sort(BtcECKey.PUBKEY_COMPARATOR); + } + + return keys; + } +} From de55592cb137b8192895a9fa138946afde272da4 Mon Sep 17 00:00:00 2001 From: julia zack Date: Mon, 28 Aug 2023 22:23:32 -0300 Subject: [PATCH 18/20] Modify size and fee calculation - FOR THE LAST TIME --- src/main/java/co/rsk/bitcoinj/script/Script.java | 3 +-- src/main/java/co/rsk/bitcoinj/wallet/Wallet.java | 16 +++------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/script/Script.java b/src/main/java/co/rsk/bitcoinj/script/Script.java index 9adab9610..720c52a23 100644 --- a/src/main/java/co/rsk/bitcoinj/script/Script.java +++ b/src/main/java/co/rsk/bitcoinj/script/Script.java @@ -610,8 +610,7 @@ public int getNumberOfBytesRequiredToSpend(@Nullable BtcECKey pubKey, @Nullable if (isPayToScriptHash()) { // scriptSig: [sig] [sig...] checkArgument(redeemScript != null, "P2SH script requires redeemScript to be spent"); - System.out.println(redeemScript.getProgram().length); - return redeemScript.getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + redeemScript.getProgram().length + 1 + 1; // 1 byte for first empty byte, 1 byte for op_notif argument + return redeemScript.getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + redeemScript.getProgram().length + 3 + 1 + 1; // 1 byte for first empty byte, 1 byte for op_notif argument } else if (isSentToMultiSig()) { // scriptSig: OP_0 [sig] [sig...] return getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + 1; diff --git a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java index cf733340a..dd67895bb 100644 --- a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java +++ b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java @@ -1003,7 +1003,7 @@ public FeeCalculation calculateFee(SendRequest req, Coin value, List originalInputs) { for (TransactionInput input : originalInputs) tx.addInput(new TransactionInput(params, tx, input.bitcoinSerialize())); From 13e4f3e333e5d24d68d03959d9ee2262efeab3e2 Mon Sep 17 00:00:00 2001 From: julia zack Date: Tue, 29 Aug 2023 07:43:27 -0300 Subject: [PATCH 19/20] Refactor --- src/main/java/co/rsk/bitcoinj/script/Script.java | 7 ++++++- src/main/java/co/rsk/bitcoinj/wallet/Wallet.java | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/co/rsk/bitcoinj/script/Script.java b/src/main/java/co/rsk/bitcoinj/script/Script.java index 720c52a23..0f79c14a4 100644 --- a/src/main/java/co/rsk/bitcoinj/script/Script.java +++ b/src/main/java/co/rsk/bitcoinj/script/Script.java @@ -610,7 +610,12 @@ public int getNumberOfBytesRequiredToSpend(@Nullable BtcECKey pubKey, @Nullable if (isPayToScriptHash()) { // scriptSig: [sig] [sig...] checkArgument(redeemScript != null, "P2SH script requires redeemScript to be spent"); - return redeemScript.getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + redeemScript.getProgram().length + 3 + 1 + 1; // 1 byte for first empty byte, 1 byte for op_notif argument + int bytesRequired = redeemScript.getNumberOfSignaturesRequiredToSpend() * SIG_SIZE; + bytesRequired += redeemScript.getProgram().length; + bytesRequired += 1; // 1 byte for first empty byte + bytesRequired += 1; // 1 byte for op_notif argument + bytesRequired += 5; // redeemScript bytes-size in hexa (approx) + return bytesRequired; } else if (isSentToMultiSig()) { // scriptSig: OP_0 [sig] [sig...] return getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + 1; diff --git a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java index dd67895bb..1bbb8e737 100644 --- a/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java +++ b/src/main/java/co/rsk/bitcoinj/wallet/Wallet.java @@ -1038,6 +1038,7 @@ public static int calculateTxBaseSize(BtcTransaction spendTx, boolean isSegwit) int inputsQuantity = spendTx.getInputs().size(); for (int i = 0; i < inputsQuantity; i++) { byte[] input = spendTx.getInput(i).bitcoinSerialize(); + System.out.println(Hex.toHexString(input)); baseSize += input.length; if (isSegwit) { @@ -1048,6 +1049,7 @@ public static int calculateTxBaseSize(BtcTransaction spendTx, boolean isSegwit) int outputsQuantity = spendTx.getOutputs().size(); for (int i = 0; i < outputsQuantity; i++) { byte[] output = spendTx.getOutput(i).bitcoinSerialize(); + System.out.println(Hex.toHexString(output)); baseSize += output.length; } From 696f2c74b50d62e9328530895a0346f72209fac2 Mon Sep 17 00:00:00 2001 From: julia zack Date: Tue, 29 Aug 2023 12:03:42 -0300 Subject: [PATCH 20/20] Add comments. Rollback SIG_SIZE change --- .../rsk/bitcoinj/core/TransactionWitness.java | 23 +++++++++++++++++-- .../java/co/rsk/bitcoinj/script/Script.java | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java index 838f76acf..df87f21d3 100644 --- a/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java +++ b/src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java @@ -60,7 +60,11 @@ public static TransactionWitness createWitness(@Nullable final TransactionSignat public static TransactionWitness createWitnessErpStandardScript(Script witnessScript, List signatures) { List pushes = new ArrayList<>(signatures.size() + 3); - pushes.add(new byte[] {}); + // the +3 is related to: + // first empty byte necessary to avoid OP_CHECKMULTISIG's bug, + // the empty byte for OP_NOTIF argument, + // the redeem script push + pushes.add(new byte[] {}); // OP_0 for (TransactionSignature signature : signatures) { pushes.add(signature.encodeToBitcoin()); } @@ -71,7 +75,12 @@ public static TransactionWitness createWitnessErpStandardScript(Script witnessSc public static TransactionWitness createWitnessErpEmergencyScript(Script witnessScript, List signatures) { List pushes = new ArrayList<>(signatures.size() + 3); - pushes.add(new byte[] {}); + // the +3 is related to: + // first empty byte necessary to avoid OP_CHECKMULTISIG's bug, + // the empty byte for OP_NOTIF argument, + // the redeem script push + + pushes.add(new byte[] {}); // OP_0 for (TransactionSignature signature : signatures) { pushes.add(signature.encodeToBitcoin()); } @@ -81,8 +90,13 @@ public static TransactionWitness createWitnessErpEmergencyScript(Script witnessS } public static TransactionWitness createWitnessErpStandardNewScript(Script witnessScript, List thresholdSignatures, int signaturesSize) { + // with this new script structure, we need to add valid and invalid signatures + // in the -reverse- order the public keys appear in it + int zeroSignaturesSize = signaturesSize - thresholdSignatures.size(); List pushes = new ArrayList<>(signaturesSize + 2); + // since we are not using OP_CHECKMULTISIG anymore, we don't need to push a first empty byte + // so we add +2 pushes, related to the OP_NOTIF and the redeem script // signatures to be used for (int i = 0; i < thresholdSignatures.size(); i++) { @@ -99,8 +113,13 @@ public static TransactionWitness createWitnessErpStandardNewScript(Script witnes } public static TransactionWitness createWitnessErpEmergencyNewScript(Script witnessScript, List thresholdSignatures, int signaturesSize) { + // with this new script structure, we need to add valid and invalid signatures + // in the inverse order the public keys appear in it + int zeroSignaturesSize = signaturesSize - thresholdSignatures.size(); List pushes = new ArrayList<>(signaturesSize + 2); + // since we are not using OP_CHECKMULTISIG anymore, we don't need to push a first empty byte + // so we add +2 pushes, related to the OP_NOTIF and the redeem script // signatures to be used for (int i = 0; i < thresholdSignatures.size(); i++) { diff --git a/src/main/java/co/rsk/bitcoinj/script/Script.java b/src/main/java/co/rsk/bitcoinj/script/Script.java index 0f79c14a4..aa24313b2 100644 --- a/src/main/java/co/rsk/bitcoinj/script/Script.java +++ b/src/main/java/co/rsk/bitcoinj/script/Script.java @@ -96,7 +96,7 @@ public enum VerifyFlag { private static final Logger log = LoggerFactory.getLogger(Script.class); public static final long MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes - public static final int SIG_SIZE = 73; + public static final int SIG_SIZE = 75; // pretty sure this should be 73. /** Max number of sigops allowed in a standard p2sh redeem script */ public static final int MAX_P2SH_SIGOPS = 15;