package com.satoshilabs.trezor;
import android.content.Context;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbRequest;
import android.util.Log;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.greenaddress.greenapi.HDKey;
import com.greenaddress.greenapi.ISigningWallet;
import com.greenaddress.greenapi.Network;
import com.greenaddress.greenapi.PreparedTransaction;
import com.satoshilabs.trezor.protobuf.TrezorMessage;
import com.satoshilabs.trezor.protobuf.TrezorMessage.Address;
import com.satoshilabs.trezor.protobuf.TrezorMessage.ButtonRequest;
import com.satoshilabs.trezor.protobuf.TrezorMessage.Entropy;
import com.satoshilabs.trezor.protobuf.TrezorMessage.EntropyRequest;
import com.satoshilabs.trezor.protobuf.TrezorMessage.Failure;
import com.satoshilabs.trezor.protobuf.TrezorMessage.Features;
import com.satoshilabs.trezor.protobuf.TrezorMessage.MessageSignature;
import com.satoshilabs.trezor.protobuf.TrezorMessage.MessageType;
import com.satoshilabs.trezor.protobuf.TrezorMessage.PassphraseRequest;
import com.satoshilabs.trezor.protobuf.TrezorMessage.PinMatrixRequest;
import com.satoshilabs.trezor.protobuf.TrezorMessage.PublicKey;
import com.satoshilabs.trezor.protobuf.TrezorMessage.SignTx;
import com.satoshilabs.trezor.protobuf.TrezorMessage.Success;
import com.satoshilabs.trezor.protobuf.TrezorMessage.TxRequest;
import com.satoshilabs.trezor.protobuf.TrezorMessage.TxSize;
import com.satoshilabs.trezor.protobuf.TrezorMessage.WordRequest;
import com.satoshilabs.trezor.protobuf.TrezorType;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.WrongNetworkException;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.script.Script;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
/* Stub for empty TrezorGUICallback */
class _TrezorGUICallback implements TrezorGUICallback {
public String pinMatrixRequest() { return ""; }
public String passphraseRequest() { return ""; }
}
public class Trezor {
private PreparedTransaction curTx;
private org.bitcoinj.core.Address curChangeAddr;
private TrezorType.HDNodeType curGaNode, curWalletNode, curRecoveryNode;
private int curSubaccount;
private final ArrayList<String> curSignatures = new ArrayList<>();
private static final String TAG = Trezor.class.getSimpleName();
public static Trezor getDevice(final Context context, final TrezorGUICallback guicall) {
final UsbManager manager = (UsbManager)context.getSystemService(Context.USB_SERVICE);
final HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
for (final UsbDevice device: deviceList.values()) {
// check if the device is TREZOR (or AvalonWallet or BWALLET) or KeepKey
if (((device.getVendorId() != 0x534c && device.getVendorId() != 0x2B24) || device.getProductId() != 0x0001) &&
(device.getVendorId() != 0x10c4 || device.getProductId() != 0xea80)) {
continue;
}
Log.i(TAG, "Hardware Wallet device found");
if (device.getInterfaceCount() < 1) {
Log.e(TAG, "Wrong interface count");
continue;
}
// use first interface
final UsbInterface iface = device.getInterface(0);
// try to find read/write endpoints
UsbEndpoint epr = null, epw = null;
for (int i = 0; i < iface.getEndpointCount(); ++i) {
final UsbEndpoint ep = iface.getEndpoint(i);
if (epr == null && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT && ep.getAddress() == 0x81) { // number = 1 ; dir = USB_DIR_IN
epr = ep;
continue;
}
if (epw == null && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT && (ep.getAddress() == 0x01 || ep.getAddress() == 0x02)) { // number = 1 ; dir = USB_DIR_OUT
epw = ep;
continue;
}
Log.e(TAG, String.format("ep %d", ep.getAddress()));
}
if (epr == null) {
Log.e(TAG, "Could not find read endpoint");
continue;
}
if (epw == null) {
Log.e(TAG, "Could not find write endpoint");
continue;
}
if (epr.getMaxPacketSize() != 64) {
Log.e(TAG, "Wrong packet size for read endpoint");
continue;
}
if (epw.getMaxPacketSize() != 64) {
Log.e(TAG, "Wrong packet size for write endpoint");
continue;
}
// try to open the device
final UsbDeviceConnection conn = manager.openDevice(device);
if (conn == null) {
Log.e(TAG, "Could not open connection");
continue;
}
final boolean claimed = conn.claimInterface(iface, true);
if (!claimed) {
Log.e(TAG, "Could not claim interface");
continue;
}
// all OK - return the class
return new Trezor(guicall, device, conn, iface, epr, epw);
}
return null;
}
private final int vendorId;
private final UsbDeviceConnection conn;
private final String serial;
private final UsbEndpoint epr, epw;
private final TrezorGUICallback guicall;
public int getVendorId() {
return vendorId;
}
private Trezor(final TrezorGUICallback guicall, final UsbDevice device, final UsbDeviceConnection conn, final UsbInterface iface, final UsbEndpoint epr, final UsbEndpoint epw) {
this.guicall = guicall;
this.vendorId = device.getVendorId();
this.conn = conn;
this.epr = epr;
this.epw = epw;
this.serial = this.conn.getSerial();
}
@Override
public String toString() {
return "TREZOR(#" + this.serial + ")";
}
private void messageWrite(final Message msg) {
final int msg_size = msg.getSerializedSize();
final String msg_name = msg.getClass().getSimpleName();
final int msg_id = MessageType.valueOf("MessageType_" + msg_name).getNumber();
Log.i(TAG, String.format("Got message: %s (%d bytes)", msg_name, msg_size));
final ByteBuffer data = ByteBuffer.allocate(32768);
data.put((byte)'#');
data.put((byte)'#');
data.put((byte)((msg_id >> 8) & 0xFF));
data.put((byte)(msg_id & 0xFF));
data.put((byte)((msg_size >> 24) & 0xFF));
data.put((byte)((msg_size >> 16) & 0xFF));
data.put((byte)((msg_size >> 8) & 0xFF));
data.put((byte)(msg_size & 0xFF));
data.put(msg.toByteArray());
while (data.position() % 63 > 0) {
data.put((byte)0);
}
final UsbRequest request = new UsbRequest();
request.initialize(conn, epw);
final int chunks = data.position() / 63;
Log.i(TAG, String.format("Writing %d chunks", chunks));
data.rewind();
for (int i = 0; i < chunks; ++i) {
final byte[] buffer = new byte[64];
buffer[0] = (byte)'?';
data.get(buffer, 1, 63);
String s = "chunk:";
for (int j = 0; j < 64; j++) {
s += String.format(" %02x", buffer[j]);
}
Log.i(TAG, s);
request.queue(ByteBuffer.wrap(buffer), 64);
conn.requestWait();
}
}
private Message parseMessageFromBytes(final MessageType type, final byte[] data) {
Message msg = null;
Log.i(TAG, String.format("Parsing %s (%d bytes):", type, data.length));
String s = "data:";
for (final byte b : data) {
s += String.format(" %02x", b);
}
Log.i(TAG, s);
try {
if (type.getNumber() == MessageType.MessageType_Success_VALUE) msg = Success.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_Failure_VALUE) msg = Failure.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_Entropy_VALUE) msg = Entropy.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_PublicKey_VALUE) msg = PublicKey.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_Features_VALUE) msg = Features.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_PinMatrixRequest_VALUE) msg = PinMatrixRequest.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_TxRequest_VALUE) msg = TxRequest.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_ButtonRequest_VALUE) msg = ButtonRequest.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_Address_VALUE) msg = Address.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_EntropyRequest_VALUE) msg = EntropyRequest.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_MessageSignature_VALUE) msg = MessageSignature.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_PassphraseRequest_VALUE) msg = PassphraseRequest.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_TxSize_VALUE) msg = TxSize.parseFrom(data);
if (type.getNumber() == MessageType.MessageType_WordRequest_VALUE) msg = WordRequest.parseFrom(data);
} catch (final InvalidProtocolBufferException e) {
Log.e(TAG, e.toString());
return null;
}
return msg;
}
private Message messageRead() {
final ByteBuffer data = ByteBuffer.allocate(32768);
final ByteBuffer buffer = ByteBuffer.allocate(64);
final ByteBuffer cur63 = ByteBuffer.allocate(64);
final UsbRequest request = new UsbRequest();
request.initialize(conn, epr);
MessageType type;
int msg_size;
for (;;) {
buffer.clear();
if (!request.queue(buffer, 64)) continue;
conn.requestWait();
final byte[] b = new byte[64];
buffer.rewind();
buffer.get(b);
Log.i(TAG, String.format("Read chunk: %d bytes", b.length));
if (b.length < 9) continue;
String s = "read:";
for (final byte bF : b) {
s += String.format(" %02x", bF);
}
Log.i(TAG, s);
final int rem = cur63.remaining(), len = b[0]&0xFF;
cur63.put(b, 1, Math.min(len, rem));
if (cur63.position() >= 63) {
final byte[] b2 = cur63.array();
if (b2[0] != (byte) '#' || b2[1] != (byte) '#') continue;
type = MessageType.valueOf((b2[2] << 8) + b2[3]);
msg_size = ((b2[4] & 0xFF) << 24) + ((b2[5] & 0xFF) << 16) + ((b2[6] & 0xFF) << 8) + (b2[7] & 0xFF);
Log.i(TAG, String.format("msg_size: %d bytes", msg_size));
data.put(b2, 8, cur63.position() - 8);
if (rem < len) data.put(b, rem + 1, len - rem);
break;
}
}
while (data.position() < msg_size) {
request.queue(buffer, 64);
conn.requestWait();
final byte[] b = buffer.array();
Log.i(TAG, String.format("Read chunk (cont): %d bytes msg size %d", b.length, msg_size));
String s = "read(cont):";
for (final byte j : b) {
s += String.format(" %02x", j);
}
Log.i(TAG, s);
data.put(b, 1, b[0] & 0xFF);
}
return parseMessageFromBytes(type, Arrays.copyOfRange(data.array(), 0, msg_size));
}
private Message send(final Message msg) {
messageWrite(msg);
return messageRead();
}
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
private static String bytesToHex(final byte[] bytes) {
final char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; ++j ) {
final int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
private String _get(final Message resp) {
switch (resp.getClass().getSimpleName()) {
case "Features": {
final TrezorMessage.Features r = (TrezorMessage.Features) resp;
return r.getMajorVersion()+"."+r.getMinorVersion()+"."+r.getPatchVersion(); }
case "Success": {
final TrezorMessage.Success r = (TrezorMessage.Success)resp;
if(r.hasMessage())return r.getMessage();
return ""; }
case "Failure":
throw new IllegalStateException();
/* User can catch ButtonRequest to Cancel by not calling _get */
case "ButtonRequest":
return _get(this.send(TrezorMessage.ButtonAck.newBuilder().build()));
case "PinMatrixRequest":
return _get(this.send(
TrezorMessage.PinMatrixAck.newBuilder().
setPin(this.guicall.pinMatrixRequest()).
build()));
case "PassphraseRequest":
return _get(this.send(
TrezorMessage.PassphraseAck.newBuilder().
/* TODO: UTF8 VS Unicode... Fight! */
setPassphrase(this.guicall.passphraseRequest()).
build()));
case "PublicKey": {
final TrezorMessage.PublicKey r = (TrezorMessage.PublicKey)resp;
if(!r.hasNode())throw new IllegalArgumentException();
final TrezorType.HDNodeType N = r.getNode();
final String NodeStr = ((N.hasDepth())?N.getDepth():"") +"%"+
((N.hasFingerprint())?N.getFingerprint():"") +"%"+
((N.hasChildNum())?N.getChildNum():"") +"%"+
((N.hasChainCode())?bytesToHex(N.getChainCode().toByteArray()):"") +"%"+
((N.hasPrivateKey())?bytesToHex(N.getPrivateKey().toByteArray()):"") +"%"+
((N.hasPublicKey())?bytesToHex(N.getPublicKey().toByteArray()):"") +"%"+
"";
if(r.hasXpub())
return NodeStr + ":!:" + r.getXpub() + ":!:" +
bytesToHex(r.getXpubBytes().toByteArray());
return NodeStr; }
case "MessageSignature": {
final TrezorMessage.MessageSignature r = (TrezorMessage.MessageSignature)resp;
return new String(Hex.encode(r.getSignature().toByteArray())); }
case "TxRequest": {
final TrezorMessage.TxRequest r = (TrezorMessage.TxRequest)resp;
final TrezorType.TransactionType ackTx;
if (r.getSerialized() != null && r.getSerialized().hasSignatureIndex()) {
curSignatures.set(r.getSerialized().getSignatureIndex(),
Hex.toHexString(r.getSerialized().getSignature().toByteArray()));
}
if (r.getRequestType().equals(TrezorType.RequestType.TXFINISHED)) {
return Joiner.on(";").join(curSignatures);
} else if (r.getRequestType().equals(TrezorType.RequestType.TXINPUT)) {
ackTx = TrezorType.TransactionType.newBuilder().
clearInputs().
addInputs(createInput(r.getDetails().hasTxHash() ? r.getDetails().getTxHash() : null,
r.getDetails().getRequestIndex())).
build();
} else if (r.getRequestType().equals(TrezorType.RequestType.TXOUTPUT)) {
if (r.getDetails().hasTxHash()) {
ackTx = TrezorType.TransactionType.newBuilder().
clearOutputs().
addBinOutputs(createBinOutput(
r.getDetails().getTxHash(),
r.getDetails().getRequestIndex())).
build();
} else {
ackTx = TrezorType.TransactionType.newBuilder().
clearOutputs().
addOutputs(createOutput(r.getDetails().getRequestIndex())).
build();
}
} else if (r.getRequestType().equals(TrezorType.RequestType.TXMETA)) {
final TrezorType.TransactionType.Builder b = TrezorType.TransactionType.newBuilder();
if (r.getDetails().hasTxHash()) {
final Transaction tx = curTx.mPrevoutRawTxs.get(Hex.toHexString(r.getDetails().getTxHash().toByteArray()));
b.setInputsCnt(tx.getInputs().size());
b.setOutputsCnt(tx.getOutputs().size());
b.setVersion((int) tx.getVersion())
.setLockTime((int) tx.getLockTime());
} else {
b.setInputsCnt(curTx.mDecoded.getInputs().size());
b.setOutputsCnt(curTx.mDecoded.getOutputs().size());
b.setVersion((int) curTx.mDecoded.getVersion())
.setLockTime((int) curTx.mDecoded.getLockTime());
}
ackTx = b.build();
} else {
return resp.getClass().getSimpleName();
}
return _get(this.send(TrezorMessage.TxAck.newBuilder().
setTx(ackTx).build())); }
}
// throw new IllegalArgumentException();
return resp.getClass().getSimpleName();
}
private TrezorType.TxOutputType createOutput(final int requestIndex) {
final TransactionOutput txOut = curTx.mDecoded.getOutputs().get(requestIndex);
final TrezorType.TxOutputType.Builder b = TrezorType.TxOutputType.newBuilder().
setAmount(txOut.getValue().longValue());
if (txOut.getScriptPubKey().isPayToScriptHash()) {
b.setAddress(txOut.getAddressFromP2SH(Network.NETWORK).toString());
if (txOut.getAddressFromP2SH(Network.NETWORK).equals(curChangeAddr)) {
b.setScriptType(TrezorType.OutputScriptType.PAYTOMULTISIG);
if (curRecoveryNode == null) {
b.setMultisig(TrezorType.MultisigRedeemScriptType.newBuilder().
clearPubkeys().
addPubkeys(TrezorType.HDNodePathType.newBuilder().
setNode(curGaNode).
clearAddressN().
addAddressN(curTx.mChangePointer)).
addPubkeys(TrezorType.HDNodePathType.newBuilder().
setNode(curWalletNode).
clearAddressN().
addAddressN(curTx.mChangePointer)).
setM(2));
} else {
b.setMultisig(TrezorType.MultisigRedeemScriptType.newBuilder().
clearPubkeys().
addPubkeys(TrezorType.HDNodePathType.newBuilder().
setNode(curGaNode).
clearAddressN().
addAddressN(curTx.mChangePointer)).
addPubkeys(TrezorType.HDNodePathType.newBuilder().
setNode(curWalletNode).
clearAddressN().
addAddressN(curTx.mChangePointer)).
addPubkeys(TrezorType.HDNodePathType.newBuilder().
setNode(curRecoveryNode).
clearAddressN().
addAddressN(curTx.mChangePointer)).
setM(2));
}
} else {
b.setScriptType(TrezorType.OutputScriptType.PAYTOSCRIPTHASH);
}
} else {
b.setAddress(txOut.getAddressFromP2PKHScript(Network.NETWORK).toString());
b.setScriptType(TrezorType.OutputScriptType.PAYTOADDRESS);
}
return b.build();
}
private TrezorType.TxOutputBinType createBinOutput(final ByteString txHash, final int requestIndex) {
final TransactionOutput out = curTx.mPrevoutRawTxs.get(Hex.toHexString(txHash.toByteArray())).getOutput(requestIndex);
return TrezorType.TxOutputBinType.newBuilder().
setAmount(out.getValue().longValue()).
setScriptPubkey(ByteString.copyFrom(out.getScriptBytes())).
build();
}
private TrezorType.TxInputType createInput(final ByteString txHash, final int requestIndex) {
final TransactionInput in;
if (txHash != null) {
in = curTx.mPrevoutRawTxs.get(Hex.toHexString(txHash.toByteArray())).getInput(requestIndex);
return TrezorType.TxInputType.newBuilder().
setPrevHash(ByteString.copyFrom(in.getOutpoint().getHash().getBytes())).
setPrevIndex((int)in.getOutpoint().getIndex()).
setSequence((int)in.getSequenceNumber()).
setScriptSig(ByteString.copyFrom(in.getScriptBytes())).
build();
} else {
in = curTx.mDecoded.getInput(requestIndex);
final Integer[] addrN;
if (curSubaccount != 0) {
addrN = new Integer[] { 3 + 0x80000000, curSubaccount + 0x80000000, 1, curTx.mPrevOutputs.get(requestIndex).pointer };
} else {
addrN = new Integer[] { 1, curTx.mPrevOutputs.get(requestIndex).pointer};
}
final TrezorType.MultisigRedeemScriptType multisig;
if (curRecoveryNode == null) {
multisig = TrezorType.MultisigRedeemScriptType.newBuilder().
clearPubkeys().
addPubkeys(TrezorType.HDNodePathType.newBuilder().
setNode(curGaNode).
clearAddressN().
addAddressN(curTx.mPrevOutputs.get(requestIndex).pointer)).
addPubkeys(TrezorType.HDNodePathType.newBuilder().
setNode(curWalletNode).
clearAddressN().
addAddressN(curTx.mPrevOutputs.get(requestIndex).pointer)).
setM(2).
build();
} else {
multisig = TrezorType.MultisigRedeemScriptType.newBuilder().
clearPubkeys().
addPubkeys(TrezorType.HDNodePathType.newBuilder().
setNode(curGaNode).
clearAddressN().
addAddressN(curTx.mPrevOutputs.get(requestIndex).pointer)).
addPubkeys(TrezorType.HDNodePathType.newBuilder().
setNode(curWalletNode).
clearAddressN().
addAddressN(curTx.mPrevOutputs.get(requestIndex).pointer)).
addPubkeys((TrezorType.HDNodePathType.newBuilder().
setNode(curRecoveryNode).
clearAddressN().
addAddressN(curTx.mPrevOutputs.get(requestIndex).pointer))).
setM(2).
build();
}
return TrezorType.TxInputType.newBuilder().
clearAddressN().
addAllAddressN(Arrays.asList(addrN)).
setPrevHash(ByteString.copyFrom(in.getOutpoint().getHash().getBytes())).
setPrevIndex((int)in.getOutpoint().getIndex()).
setSequence((int) in.getSequenceNumber()).
setScriptType(TrezorType.InputScriptType.SPENDMULTISIG).
setMultisig(multisig).
build();
}
}
public String MessageGetPublicKey(Integer[] addrn) {
return _get(this.send(
TrezorMessage.GetPublicKey.newBuilder().
clearAddressN().
addAllAddressN(Arrays.asList(addrn)).
build()));
}
public ECKey.ECDSASignature MessageSignMessage(Integer[] addrn, String message) {
byte[] sigCompact = Hex.decode(_get(this.send(
TrezorMessage.SignMessage.newBuilder().
clearAddressN().
addAllAddressN(Arrays.asList(addrn)).
setMessage(ByteString.copyFromUtf8(message)).
build())));
return new ECKey.ECDSASignature(
new BigInteger(1, Arrays.copyOfRange(sigCompact, 1, 33)),
new BigInteger(1, Arrays.copyOfRange(sigCompact, 33, 65)));
}
public List<Integer> getFirmwareVersion() {
Iterable<String> version = Splitter.on(".").split(_get(this.send(
TrezorMessage.Initialize.newBuilder().build()
)));
LinkedList<Integer> versionInts = new LinkedList<>();
for (String s : version) {
versionInts.add(Integer.valueOf(s));
}
return versionInts;
}
public List<byte[]> MessageSignTx(final PreparedTransaction ptx, final String coinName) {
curTx = ptx;
curSubaccount = ptx.mSubAccount;
final DeterministicKey[] serverKeys = HDKey.getGAPublicKeys(ptx.mSubAccount, ptx.mChangePointer);
curGaNode = TrezorType.HDNodeType.newBuilder().
setDepth(serverKeys[0].getDepth()).
setFingerprint(0).
setChildNum(serverKeys[0].getChildNumber().getI()).
setPublicKey(ByteString.copyFrom(serverKeys[0].getPubKey())).
setChainCode(ByteString.copyFrom(serverKeys[0].getChainCode())).
build();
curChangeAddr = null;
if (serverKeys[1] != null) {
// We have a change pointer
final List<ECKey> pubkeys = new ArrayList<>();
pubkeys.add(ECKey.fromPublicOnly(serverKeys[1].getPubKeyPoint()));
final Integer[] intArray;
if (ptx.mSubAccount != 0) {
intArray = new Integer[]{3 + 0x80000000, ptx.mSubAccount + 0x80000000, HDKey.BRANCH_REGULAR, ptx.mChangePointer};
} else {
intArray = new Integer[]{HDKey.BRANCH_REGULAR, ptx.mChangePointer};
}
final String[] xpub = MessageGetPublicKey(intArray).split("%", -1);
final String pkHex = xpub[xpub.length - 2];
pubkeys.add(ECKey.fromPublicOnly(Hex.decode(pkHex)));
curRecoveryNode = null;
if (ptx.mTwoOfThreeBackupChaincode != null) {
final DeterministicKey keys[];
keys = HDKey.getRecoveryKeys(ptx.mTwoOfThreeBackupChaincode, ptx.mTwoOfThreeBackupPubkey,
ptx.mChangePointer);
curRecoveryNode = TrezorType.HDNodeType.newBuilder().
setDepth(keys[0].getDepth()).
setFingerprint(0).
setChildNum(keys[0].getChildNumber().getI()).
setChainCode(ByteString.copyFrom(keys[0].getChainCode())).
setPublicKey(ByteString.copyFrom(keys[0].getPubKey())).
build();
pubkeys.add(ECKey.fromPublicOnly(keys[1].getPubKeyPoint()));
}
final Script changeScript = new Script(Script.createMultiSigOutputScript(2, pubkeys));
try {
curChangeAddr = new org.bitcoinj.core.Address(Network.NETWORK,
Network.NETWORK.getP2SHHeader(),
Utils.sha256hash160(changeScript.getProgram()));
} catch (WrongNetworkException e) {
}
}
final Integer[] intArray2;
if (ptx.mSubAccount != 0) {
intArray2 = new Integer[]{3 + 0x80000000, ptx.mSubAccount + 0x80000000, HDKey.BRANCH_REGULAR};
} else {
intArray2 = new Integer[]{HDKey.BRANCH_REGULAR};
}
final String[] xpub2 = MessageGetPublicKey(intArray2).split("%", -1);
final String pkHex2 = xpub2[xpub2.length-2];
final String chainCodeHex2 = xpub2[xpub2.length-4];
curWalletNode = TrezorType.HDNodeType.newBuilder().
setDepth(1).
setFingerprint(0).
setChildNum(1).
setPublicKey(ByteString.copyFrom(Hex.decode(pkHex2))).
setChainCode(ByteString.copyFrom(Hex.decode(chainCodeHex2))).
build();
curSignatures.clear();
for (int i = 0; i < curTx.mDecoded.getInputs().size(); ++i) {
curSignatures.add("");
}
final LinkedList<byte[]> signaturesList = new LinkedList<>();
final String[] signatures = _get(this.send(
SignTx.newBuilder().
setInputsCount(ptx.mDecoded.getInputs().size()).
setOutputsCount(ptx.mDecoded.getOutputs().size()).
setCoinName(coinName).
build())).split(";");
for (final String sig: signatures) {
signaturesList.add(ISigningWallet.getTxSignature(ECKey.ECDSASignature.decodeFromDER(Hex.decode(sig))));
}
return signaturesList;
}
}