Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Final segwit poc #69

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e1095b5
Create new method createP2SHP2WSHOutputScript
marcos-iov Jul 21, 2023
cb9577c
Replace sha256hash160 with hash160. Refactor
julia-zack Jul 24, 2023
11b9183
Use createP2SHOutputScript implementation that receives the script
marcos-iov Jul 24, 2023
e7fcfe7
Add test for function hash160
julia-zack Jul 25, 2023
5bc92f6
Add createWitnessScript and needed constructor values into TxWitness …
julia-zack Jul 27, 2023
c304dcb
Merge pull request #62 from rsksmart/modify_TransactionWitness
marcos-iov Jul 28, 2023
b5369f1
Add hashForWitnessSignature, createWitnessErpScript and createP2shP2w…
julia-zack Aug 3, 2023
1d15303
Update src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java
marcos-iov Aug 3, 2023
8045905
Merge pull request #63 from rsksmart/sign_with_witness_fed
marcos-iov Aug 3, 2023
38ec8fa
Add witnessEmergencyScript, remove OP_0NOTEQUAL from redeem and refac…
julia-zack Aug 8, 2023
fcdb7c2
Correct OP_NOTIF argument
julia-zack Aug 11, 2023
c94b9e6
Merge pull request #64 from rsksmart/refactor_and_add_scripts
marcos-iov Aug 11, 2023
25f6d80
Add function to create a flyover p2wsh script
marcos-iov Aug 11, 2023
a70f5ee
Merge pull request #66 from rsksmart/add_reedem_with_flyover
marcos-iov Aug 11, 2023
2390c0b
Modify Wallet class to calculate fee for segwit
julia-zack Aug 18, 2023
8e048ea
Add isSegwit parameter to SendRequest. Correct calculations in Wallet…
julia-zack Aug 25, 2023
0e95d9e
Remove unnecessary code (prints and etc)
julia-zack Aug 25, 2023
c7f06dd
Add bytesToAdd to all txs totalSize
julia-zack Aug 25, 2023
575c758
Add indentation
julia-zack Aug 28, 2023
25f9abe
Add indentation
julia-zack Aug 28, 2023
b30262c
Merge pull request #68 from rsksmart/segwit_fee_calculation
julia-zack Aug 28, 2023
0e2a246
Refactor. Modify size and fee calculation
julia-zack Aug 28, 2023
de55592
Modify size and fee calculation - FOR THE LAST TIME
julia-zack Aug 29, 2023
13e4f3e
Refactor
julia-zack Aug 29, 2023
696f2c7
Add comments. Rollback SIG_SIZE change
julia-zack Aug 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/co/rsk/bitcoinj/core/BtcECKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
108 changes: 107 additions & 1 deletion src/main/java/co/rsk/bitcoinj/core/BtcTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -105,7 +106,6 @@ public int compare(final BtcTransaction tx1, final BtcTransaction tx2) {
private ArrayList<TransactionOutput> outputs;
private ArrayList<TransactionWitness> witnesses;


private long lockTime;

// This is either the time the transaction was broadcast as measured from the local clock, or the time from the
Expand Down Expand Up @@ -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);
}

/**
* <p>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.</p>
*
* <p>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)</p>
*
* @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);
Expand Down
72 changes: 71 additions & 1 deletion src/main/java/co/rsk/bitcoinj/core/TransactionWitness.java
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -13,12 +15,22 @@ public static TransactionWitness getEmpty() {
return empty;
}

private List<byte[]> pushes;
private final List<byte[]> pushes;

public TransactionWitness(int pushCount) {
pushes = new ArrayList<byte[]>(Math.min(pushCount, Utils.MAX_INITIAL_ARRAY_LENGTH));
}

public static TransactionWitness of(List<byte[]> pushes) {
return new TransactionWitness(pushes);
}

private TransactionWitness(List<byte[]> pushes) {
for (byte[] push : pushes)
Objects.requireNonNull(push);
this.pushes = pushes;
}

public byte[] getPush(int i) {
return pushes.get(i);
}
Expand Down Expand Up @@ -46,6 +58,64 @@ public static TransactionWitness createWitness(@Nullable final TransactionSignat
return witness;
}

public static TransactionWitness createWitnessErpStandardScript(Script witnessScript, List<TransactionSignature> signatures) {
List<byte[]> pushes = new ArrayList<>(signatures.size() + 3);
julia-zack marked this conversation as resolved.
Show resolved Hide resolved
pushes.add(new byte[] {});
julia-zack marked this conversation as resolved.
Show resolved Hide resolved
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 createWitnessErpEmergencyScript(Script witnessScript, List<TransactionSignature> signatures) {
List<byte[]> 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 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 createWitnessErpStandardNewScript(Script witnessScript, List<TransactionSignature> thresholdSignatures, int signaturesSize) {
julia-zack marked this conversation as resolved.
Show resolved Hide resolved
int zeroSignaturesSize = signaturesSize - thresholdSignatures.size();
List<byte[]> 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[] {}); // OP_NOTIF argument
pushes.add(witnessScript.getProgram());
return TransactionWitness.of(pushes);
}

public static TransactionWitness createWitnessErpEmergencyNewScript(Script witnessScript, List<TransactionSignature> thresholdSignatures, int signaturesSize) {
int zeroSignaturesSize = signaturesSize - thresholdSignatures.size();
List<byte[]> 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);
}

public byte[] getScriptBytes() {
if (getPushCount() == 0)
return new byte[0];
Expand Down
16 changes: 10 additions & 6 deletions src/main/java/co/rsk/bitcoinj/core/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,19 @@ 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);

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[] ripemdHash = new byte[20];
digest.doFinal(ripemdHash, 0);
return ripemdHash;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/co/rsk/bitcoinj/crypto/DeterministicKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -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()}. */
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
13 changes: 9 additions & 4 deletions src/main/java/co/rsk/bitcoinj/script/Script.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
julia-zack marked this conversation as resolved.
Show resolved Hide resolved
/** Max number of sigops allowed in a standard p2sh redeem script */
public static final int MAX_P2SH_SIGOPS = 15;

Expand Down Expand Up @@ -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()));
}

/**
Expand Down Expand Up @@ -610,7 +610,12 @@ public int getNumberOfBytesRequiredToSpend(@Nullable BtcECKey pubKey, @Nullable
if (isPayToScriptHash()) {
// scriptSig: <sig> [sig] [sig...] <redeemscript>
checkArgument(redeemScript != null, "P2SH script requires redeemScript to be spent");
return redeemScript.getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + redeemScript.getProgram().length;
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] [sig...]
return getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + 1;
Expand Down Expand Up @@ -1263,7 +1268,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)
Expand Down
Loading