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

Segwit poc #61

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 45 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,38 @@ public static TransactionWitness createWitness(@Nullable final TransactionSignat
return witness;
}

public static TransactionWitness createWitnessScript(Script witnessScript, List<TransactionSignature> signatures) {
List<byte[]> 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 static TransactionWitness createWitnessErpScript(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[] {}); // 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 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
@@ -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;
Expand Down Expand Up @@ -84,6 +85,54 @@ 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<ScriptChunk> chunks) {
return RedeemScriptValidator.hasP2shErpRedeemScriptStructure(chunks);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/co/rsk/bitcoinj/script/Script.java
Original file line number Diff line number Diff line change
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 @@ -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)
Expand Down
22 changes: 18 additions & 4 deletions src/main/java/co/rsk/bitcoinj/script/ScriptBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -270,13 +271,13 @@ public static Script createInputScript(@Nullable TransactionSignature signature)
public static Script createMultiSigOutputScript(int threshold, List<BtcECKey> 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();
}
Expand Down Expand Up @@ -411,10 +412,23 @@ 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);
}

/**
* 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();

return ScriptBuilder.createP2SHOutputScript(witnessScript);
}

/**
* 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.
Expand Down
Loading