From e1095b56343942eb24987573319bbfcd5248b3a4 Mon Sep 17 00:00:00 2001 From: Marcos Date: Fri, 21 Jul 2023 16:43:44 -0300 Subject: [PATCH 01/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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(); }