package com.greenaddress.greenapi;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Message;
import org.bitcoinj.core.MessageSerializer;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.ProtocolException;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOptions;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.TransactionWitness;
import org.bitcoinj.core.VarInt;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import static org.bitcoinj.core.Utils.uint32ToByteStreamLE;
public class ElementsTransaction extends Transaction {
List<List<byte[]>> outWitness;
public ElementsTransaction(final NetworkParameters params) {
super(params);
outWitness = new ArrayList<>();
}
public ElementsTransaction(final NetworkParameters params, final byte[] payload, final int offset, final Message parent, final MessageSerializer setSerializer, final int length) throws ProtocolException {
super(params, payload, offset, parent, setSerializer, length);
outWitness = new ArrayList<>();
}
public void addOutWitness(final byte[] surjectionProof, final byte[] rangeProof, final byte[] nonceCommitment) {
outWitness.add(new ArrayList<byte[]>());
outWitness.get(outWitness.size() - 1).add(surjectionProof);
outWitness.get(outWitness.size() - 1).add(rangeProof);
outWitness.get(outWitness.size() - 1).add(nonceCommitment);
}
@Override
protected void readOutputs() {
final long numOutputs = readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(numOutputs);
outputs = new ArrayList<>((int) numOutputs);
for (long i = 0; i < numOutputs; i++) {
final TransactionOutput output = new ElementsTransactionOutput(params, this, payload, cursor, serializer);
outputs.add(output);
final int len = (output.getValue().getValue() != 0) ? 42 : 66; // 33+9 or 33+33
final long scriptLen = readVarInt(len);
optimalEncodingMessageSize += len + VarInt.sizeOf(scriptLen) + scriptLen;
cursor += scriptLen;
}
}
public ElementsTransactionOutput addOutput(final byte[] assetId, final Coin amount, final ConfidentialAddress confidentialAddress) {
final ElementsTransactionOutput eto = new ElementsTransactionOutput(params, this, assetId, amount, confidentialAddress);
addOutput(eto);
return eto;
}
@Override
protected void bitcoinSerializeToStream(final OutputStream stream, final int transactionOptions) throws IOException {
final boolean witSupported = (protocolVersion >= NetworkParameters.ProtocolVersion.WITNESS_VERSION.getBitcoinProtocolVersion())
&& (transactionOptions & TransactionOptions.WITNESS) != 0;
final boolean serializeWit = hasWitness() && witSupported;
uint32ToByteStreamLE(getVersion(), stream);
if (serializeWit) {
stream.write(new byte[]{0, (byte) (!outWitness.isEmpty() ? 3 : 1)});
} else if (!outWitness.isEmpty() && (transactionOptions & TransactionOptions.WITNESS) != 0) {
stream.write(new byte[]{0, 2});
}
stream.write(new VarInt(getInputs().size()).encode());
for (final TransactionInput in : getInputs())
in.bitcoinSerialize(stream);
stream.write(new VarInt(outputs.size()).encode());
for (final TransactionOutput out : outputs)
out.bitcoinSerialize(stream);
if (serializeWit) {
for (int i = 0; i < getInputs().size(); i++) {
final TransactionWitness witness = getWitness(i);
stream.write(new VarInt(witness.getPushCount()).encode());
for (int y = 0; y < witness.getPushCount(); y++) {
final byte[] push = witness.getPush(y);
stream.write(new VarInt(push.length).encode());
stream.write(push);
}
}
}
if ((transactionOptions & TransactionOptions.WITNESS) != 0) {
for (final List<byte[]> outwit : outWitness) {
for (final byte[] ow : outwit) {
stream.write(new VarInt(ow.length).encode());
stream.write(ow);
}
}
}
uint32ToByteStreamLE(getLockTime(), stream);
}
protected void parse() throws ProtocolException {
final boolean witSupported = (protocolVersion >= NetworkParameters.ProtocolVersion.WITNESS_VERSION.getBitcoinProtocolVersion())
&& (transactionOptions & TransactionOptions.WITNESS) != 0;
cursor = offset;
version = readUint32();
optimalEncodingMessageSize = 4;
// First come the inputs.
readInputs();
byte flags = 0;
if (witSupported && getInputs().isEmpty()) {
flags = readBytes(1)[0];
optimalEncodingMessageSize += 1;
if (flags != 0) {
readInputs();
readOutputs();
} else {
outputs = new ArrayList<>(0);
}
} else {
readOutputs();
}
if (((flags & 1) != 0) && witSupported) {
flags ^= 1;
readWitness();
}
if (((flags & 2) != 0) && witSupported) {
flags ^= 2;
readOutWitness();
}
if (flags != 0) {
throw new ProtocolException("Unknown transaction optional data");
}
lockTime = readUint32();
optimalEncodingMessageSize += 4;
length = cursor - offset;
witnesses = witnesses == null ? new ArrayList<TransactionWitness>() : witnesses;
}
protected void readOutWitness() {
outWitness = new ArrayList<>(getOutputs().size());
for (int i = 0; i < getOutputs().size(); i++) {
final long pushCount = 3;
outWitness.add(new ArrayList<byte[]>());
for (int y = 0; y < pushCount; y++) {
final long pushSize = readVarInt();
optimalEncodingMessageSize += VarInt.sizeOf(pushSize) + pushSize;
outWitness.get(outWitness.size() - 1).add(readBytes((int) pushSize));
}
}
}
}