Skip to content

Commit

Permalink
Transaction can serialize and deserialize according to Segwit (BIP144…
Browse files Browse the repository at this point in the history
…). This adds TransactionWitnesses to the wallet protobuf, too.

Based on code by:
  NicolasDorier <nicolas.dorier@gmail.com>
  Oscar Guindzberg <oscar.guindzberg@gmail.com>
  sstone <sstone@users.noreply.github.com>
  • Loading branch information
Andreas Schildbach committed Apr 6, 2018
1 parent 7c270c2 commit b9c983e
Show file tree
Hide file tree
Showing 13 changed files with 1,117 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ public static enum ProtocolVersion {
MINIMUM(70000),
PONG(60001),
BLOOM_FILTER(70000),
WITNESS_VERSION(70012),
CURRENT(70012);

private final int bitcoinProtocol;
Expand Down
104 changes: 96 additions & 8 deletions core/src/main/java/org/bitcoinj/core/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,13 @@ public Transaction(NetworkParameters params, byte[] payload, @Nullable Message p
@Override
public Sha256Hash getHash() {
if (hash == null) {
hash = Sha256Hash.wrapReversed(Sha256Hash.hashTwice(unsafeBitcoinSerialize()));
ByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(length < 32 ? 32 : length + 32);
try {
bitcoinSerializeToStream(stream, false);
} catch (IOException e) {
// Cannot happen, we are serializing to a memory stream.
}
hash = Sha256Hash.wrapReversed(Sha256Hash.hashTwice(stream.toByteArray()));
}
return hash;
}
Expand Down Expand Up @@ -563,14 +569,41 @@ protected static int calcLength(byte[] buf, int offset) {
return cursor - offset + 4;
}

/**
* Deserialize according to <a href="https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki">BIP144</a> or
* the <a href="https://en.bitcoin.it/wiki/Protocol_documentation#tx">classic format</a>, depending on if the
* transaction is segwit or not.
*/
@Override
protected void parse() throws ProtocolException {
cursor = offset;
optimalEncodingMessageSize = 4;

// version
version = readUint32();
optimalEncodingMessageSize = 4;
// peek at marker
byte marker = payload[cursor];
boolean useSegwit = marker == 0;
// marker, flag
if (useSegwit) {
readBytes(2);
optimalEncodingMessageSize += 2;
}
// txin_count, txins
parseInputs();
// txout_count, txouts
parseOutputs();
// script_witnesses
if (useSegwit)
parseWitnesses();
// lock_time
lockTime = readUint32();
optimalEncodingMessageSize += 4;

// First come the inputs.
length = cursor - offset;
}

private void parseInputs() {
long numInputs = readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(numInputs);
inputs = new ArrayList<>(Math.min((int) numInputs, MAX_INITIAL_INPUTS_OUTPUTS_SIZE));
Expand All @@ -581,7 +614,9 @@ protected void parse() throws ProtocolException {
optimalEncodingMessageSize += TransactionOutPoint.MESSAGE_LENGTH + VarInt.sizeOf(scriptLen) + scriptLen + 4;
cursor += scriptLen + 4;
}
// Now the outputs
}

private void parseOutputs() {
long numOutputs = readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(numOutputs);
outputs = new ArrayList<>(Math.min((int) numOutputs, MAX_INITIAL_INPUTS_OUTPUTS_SIZE));
Expand All @@ -592,9 +627,30 @@ protected void parse() throws ProtocolException {
optimalEncodingMessageSize += 8 + VarInt.sizeOf(scriptLen) + scriptLen;
cursor += scriptLen;
}
lockTime = readUint32();
optimalEncodingMessageSize += 4;
length = cursor - offset;
}

private void parseWitnesses() {
int numWitnesses = inputs.size();
for (int i = 0; i < numWitnesses; i++) {
long pushCount = readVarInt();
TransactionWitness witness = new TransactionWitness((int) pushCount);
getInput(i).setWitness(witness);
optimalEncodingMessageSize += VarInt.sizeOf(pushCount);
for (int y = 0; y < pushCount; y++) {
long pushSize = readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(pushSize) + pushSize;
byte[] push = readBytes((int) pushSize);
witness.setPush(y, push);
}
}
}

/** @return true of the transaction has any witnesses in any of its inputs */
public boolean hasWitnesses() {
for (TransactionInput in : inputs)
if (in.hasWitness())
return true;
return false;
}

public int getOptimalEncodingMessageSize() {
Expand Down Expand Up @@ -694,6 +750,7 @@ public String toString(@Nullable AbstractBlockChain chain) {
return s.toString();
}
if (!inputs.isEmpty()) {
int i = 0;
for (TransactionInput in : inputs) {
s.append(" ");
s.append("in ");
Expand All @@ -704,6 +761,11 @@ public String toString(@Nullable AbstractBlockChain chain) {
final Coin value = in.getValue();
if (value != null)
s.append(" ").append(value.toFriendlyString());
if (in.hasWitness()) {
s.append("\n ");
s.append("witness:");
s.append(in.getWitness());
}
s.append("\n ");
s.append("outpoint:");
final TransactionOutPoint outpoint = in.getOutpoint();
Expand All @@ -730,6 +792,7 @@ public String toString(@Nullable AbstractBlockChain chain) {
s.append("[exception: ").append(e.getMessage()).append("]");
}
s.append('\n');
i++;
}
} else {
s.append(" ");
Expand Down Expand Up @@ -1129,17 +1192,42 @@ public Sha256Hash hashForSignature(int inputIndex, byte[] connectedScript, byte

@Override
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
boolean useSegwit = hasWitnesses()
&& protocolVersion >= NetworkParameters.ProtocolVersion.WITNESS_VERSION.getBitcoinProtocolVersion();
bitcoinSerializeToStream(stream, useSegwit);
}

/**
* Serialize according to <a href="https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki">BIP144</a> or the
* <a href="https://en.bitcoin.it/wiki/Protocol_documentation#tx">classic format</a>, depending on if segwit is
* desired.
*/
protected void bitcoinSerializeToStream(OutputStream stream, boolean useSegwit) throws IOException {
// version
uint32ToByteStreamLE(version, stream);
// marker, flag
if (useSegwit) {
stream.write(0);
stream.write(1);
}
// txin_count, txins
stream.write(new VarInt(inputs.size()).encode());
for (TransactionInput in : inputs)
in.bitcoinSerialize(stream);
// txout_count, txouts
stream.write(new VarInt(outputs.size()).encode());
for (TransactionOutput out : outputs)
out.bitcoinSerialize(stream);
// script_witnisses
if (useSegwit) {
for (TransactionInput in : inputs) {
in.getWitness().bitcoinSerializeToStream(stream);
}
}
// lock_time
uint32ToByteStreamLE(lockTime, stream);
}


/**
* Transactions can have an associated lock time, specified either as a block height or in seconds since the
* UNIX epoch. A transaction is not allowed to be confirmed by miners until the lock time is reached, and
Expand Down
29 changes: 28 additions & 1 deletion core/src/main/java/org/bitcoinj/core/TransactionInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ public class TransactionInput extends ChildMessage {
@Nullable
private Coin value;

private TransactionWitness witness;

/**
* Creates an input that connects to nothing - used only in creation of coinbase transactions.
*/
Expand Down Expand Up @@ -265,6 +267,31 @@ public Coin getValue() {
return value;
}

/**
* Get the transaction witness of this input.
*
* @return the witness of the input
*/
public TransactionWitness getWitness() {
return witness != null ? witness : TransactionWitness.EMPTY;
}

/**
* Set the transaction witness of an input.
*/
public void setWitness(TransactionWitness witness) {
this.witness = witness;
}

/**
* Determine if the transaction has witnesses.
*
* @return true if the transaction has witnesses
*/
public boolean hasWitness() {
return witness != null && witness.getPushCount() != 0;
}

public enum ConnectionResult {
NO_SUCH_TX,
ALREADY_SPENT,
Expand Down Expand Up @@ -501,7 +528,7 @@ public String toString() {
s.append(": COINBASE");
} else {
s.append(" for [").append(outpoint).append("]: ").append(getScriptSig());
String flags = Joiner.on(", ").skipNulls().join(
String flags = Joiner.on(", ").skipNulls().join(hasWitness() ? "witness" : null,
hasSequence() ? "sequence: " + Long.toHexString(sequence) : null,
isOptInFullRBF() ? "opts into full RBF" : null);
if (!flags.isEmpty())
Expand Down
79 changes: 79 additions & 0 deletions core/src/main/java/org/bitcoinj/core/TransactionWitness.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.bitcoinj.core;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TransactionWitness {
public static final TransactionWitness EMPTY = new TransactionWitness(0);

private final byte[][] pushes;

public TransactionWitness(int pushCount) {
pushes = new byte[pushCount][];
}

public byte[] getPush(int i) {
return pushes[i];
}

public int getPushCount() {
return pushes.length;
}

public void setPush(int i, byte[] value) {
pushes[i] = value;
}

protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
stream.write(new VarInt(pushes.length).encode());
for (int i = 0; i < pushes.length; i++) {
byte[] push = pushes[i];
stream.write(new VarInt(push.length).encode());
stream.write(push);
}
}

@Override
public String toString() {
List<String> stringPushes = new ArrayList<>();
for (int j = 0; j < this.getPushCount(); j++) {
byte[] push = this.getPush(j);
if (push != null) {
stringPushes.add(Utils.HEX.encode(push));
} else {
stringPushes.add("NULL");
}
}
return Utils.SPACE_JOINER.join(stringPushes);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TransactionWitness other = (TransactionWitness) o;
return Arrays.deepEquals(pushes, other.pushes);
}

@Override
public int hashCode() {
return Arrays.deepHashCode(pushes);
}
}
Loading

0 comments on commit b9c983e

Please sign in to comment.