/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
*
* 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 com.btchip;
import com.btchip.comm.BTChipTransport;
import com.btchip.utils.BIP32Utils;
import com.btchip.utils.BufferUtils;
import com.btchip.utils.CoinFormatUtils;
import com.btchip.utils.Dump;
import com.btchip.utils.VarintUtils;
import java.io.ByteArrayOutputStream;
import java.io.PipedInputStream;
public class BTChipDongle implements BTChipConstants {
private BTChipFirmware firmwareVersion;
public enum OperationMode {
WALLET(0x01),
RELAXED_WALLET(0x02),
SERVER(0x04),
DEVELOPER(0x08);
private int value;
OperationMode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public enum Feature {
UNCOMPRESSED_KEYS(0x01),
RFC6979(0x02),
FREE_SIGHASHTYPE(0x04),
NO_2FA_P2SH(0x08);
private int value;
Feature(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public enum UserConfirmation {
NONE(0x00),
KEYBOARD(0x01),
KEYCARD_DEPRECATED(0x02),
KEYCARD_SCREEN(0x03),
KEYCARD(0x04),
KEYCARD_NFC(0x05);
private int value;
UserConfirmation(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public class BTChipPublicKey {
private byte[] publicKey;
private String address;
private byte[] chainCode;
public BTChipPublicKey(byte[] publicKey, String address, byte[] chainCode) {
this.publicKey = publicKey;
this.address = address;
this.chainCode = chainCode;
}
public byte[] getPublicKey() {
return publicKey;
}
public String getAddress() {
return address;
}
public byte[] getChainCode() {
return chainCode;
}
@Override
public String toString() {
return String.format("Address %s public key %s chaincode %s", address, Dump.dump(publicKey), Dump.dump(chainCode));
}
}
public class BTChipSignature {
private byte[] signature;
private int yParity;
public BTChipSignature(byte[] signature, int yParity) {
this.signature = signature;
this.yParity = yParity;
}
public byte[] getSignature() {
return signature;
}
public int getYParity() {
return yParity;
}
@Override
public String toString() {
return String.format("Signature %s y parity %s", Dump.dump(signature), yParity);
}
}
public class BTChipFirmware {
private int major;
private int minor;
private int patch;
private boolean compressedKeys;
public BTChipFirmware(int major, int minor, int patch, boolean compressedKeys) {
this.major = major;
this.minor = minor;
this.patch = patch;
this.compressedKeys = compressedKeys;
}
public int getMajor() {
return major;
}
public int getMinor() {
return minor;
}
public int getPatch() {
return patch;
}
public boolean isCompressedKey() {
return compressedKeys;
}
@Override
public String toString() {
return String.format("%s.%s.%s compressed keys %b", major, minor, patch, compressedKeys);
}
}
public class BTChipInput {
private byte[] value;
private byte[] sequence;
private boolean trusted;
private boolean segwit;
public BTChipInput(byte[] value, byte[] sequence, boolean trusted, boolean segwit) {
this.value = value;
this.sequence = sequence;
this.trusted = trusted;
this.segwit = segwit;
}
public byte[] getValue() {
return value;
}
public boolean isTrusted() {
return trusted;
}
public byte[] getSequence() {
return sequence;
}
public boolean isSegwit() {
return segwit;
}
@Override
public String toString() {
return String.format("Value %s trusted %b sequence %s segwit %b", Dump.dump(value), trusted, (sequence != null ? Dump.dump(sequence) : ""), segwit);
}
}
public class BTChipOutput {
private byte[] value;
private UserConfirmation userConfirmation;
public BTChipOutput(byte[] value, UserConfirmation userConfirmation) {
this.value = value;
this.userConfirmation = userConfirmation;
}
public byte[] getValue() {
return value;
}
public boolean isConfirmationNeeded() {
return (!userConfirmation.equals(UserConfirmation.NONE));
}
public UserConfirmation getUserConfirmation() {
return userConfirmation;
}
@Override
public String toString() {
return String.format("Value %s confirmation type %s", Dump.dump(value), userConfirmation);
}
}
public class BTChipOutputKeycard extends BTChipOutput {
private byte[] keycardIndexes;
public BTChipOutputKeycard(byte[] value, UserConfirmation userConfirmation, byte[] keycardIndexes) {
super(value, userConfirmation);
this.keycardIndexes = keycardIndexes;
}
public byte[] getKeycardIndexes() {
return keycardIndexes;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(super.toString());
buffer.append(" address indexes ");
for (int i=0; i<keycardIndexes.length; i++) {
buffer.append(i).append(" ");
}
return buffer.toString();
}
}
public class BTChipOutputKeycardScreen extends BTChipOutputKeycard {
private byte[] screenInfo;
public BTChipOutputKeycardScreen(byte[] value, UserConfirmation userConfirmation, byte[] keycardIndexes, byte[] screenInfo) {
super(value, userConfirmation, keycardIndexes);
this.screenInfo = screenInfo;
}
public byte[] getScreenInfo() {
return screenInfo;
}
@Override
public String toString() {
return String.format("%s screen data %s", super.toString(), Dump.dump(screenInfo));
}
}
private BTChipTransport transport;
private int lastSW;
private boolean mUnderstandsMultipleOutputs;
private static final int OK[] = { SW_OK };
private static final byte DUMMY[] = { 0 };
public BTChipDongle(BTChipTransport transport) {
this.transport = transport;
}
public BTChipDongle(BTChipTransport transport, boolean understandsMultipleOutputs) {
this.transport = transport;
this.mUnderstandsMultipleOutputs = understandsMultipleOutputs;
}
public boolean understandsMultipleOutputs() {
return mUnderstandsMultipleOutputs;
}
public BTChipTransport getTransport() {
return transport;
}
public void setTransport(BTChipTransport transport) {
this.transport = transport;
}
private byte[] exchange(byte[] apdu) throws BTChipException {
byte[] response;
try {
response = transport.exchange(apdu).get();
}
catch(Exception e) {
throw new BTChipException("I/O error", e);
}
if (response.length < 2) {
throw new BTChipException("Truncated response");
}
lastSW = ((response[response.length - 2] & 0xff) << 8) |
response[response.length - 1] & 0xff;
byte[] result = new byte[response.length - 2];
System.arraycopy(response, 0, result, 0, response.length - 2);
return result;
}
private byte[] exchangeCheck(byte[] apdu, int acceptedSW[]) throws BTChipException {
byte[] response = exchange(apdu);
if (acceptedSW == null) {
return response;
}
for (int SW : acceptedSW) {
if (lastSW == SW) {
return response;
}
}
throw new BTChipException("Invalid status", lastSW);
}
private byte[] exchangeApdu(byte cla, byte ins, byte p1, byte p2, byte[] data, int acceptedSW[]) throws BTChipException {
byte[] apdu = new byte[data.length + 5];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte)(data.length);
System.arraycopy(data, 0, apdu, 5, data.length);
return exchangeCheck(apdu, acceptedSW);
}
private byte[] exchangeApdu(byte cla, byte ins, byte p1, byte p2, int length, int acceptedSW[]) throws BTChipException {
byte[] apdu = new byte[5];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte)(length);
return exchangeCheck(apdu, acceptedSW);
}
private byte[] exchangeApduSplit(byte cla, byte ins, byte p1, byte p2, byte[] data, int acceptedSW[]) throws BTChipException {
int offset = 0;
byte[] result = null;
while (offset < data.length) {
int blockLength = ((data.length - offset) > 255 ? 255 : data.length - offset);
byte[] apdu = new byte[blockLength + 5];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte)(blockLength);
System.arraycopy(data, offset, apdu, 5, blockLength);
result = exchangeCheck(apdu, acceptedSW);
offset += blockLength;
}
return result;
}
private byte[] exchangeApduSplit2(byte cla, byte ins, byte p1, byte p2, byte[] data, byte[] data2, int acceptedSW[]) throws BTChipException {
int offset = 0;
byte[] result = null;
int maxBlockSize = 255 - data2.length;
while (offset < data.length) {
int blockLength = ((data.length - offset) > maxBlockSize ? maxBlockSize : data.length - offset);
boolean lastBlock = ((offset + blockLength) == data.length);
byte[] apdu = new byte[blockLength + 5 + (lastBlock ? data2.length : 0)];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte)(blockLength + (lastBlock ? data2.length : 0));
System.arraycopy(data, offset, apdu, 5, blockLength);
if (lastBlock) {
System.arraycopy(data2, 0, apdu, 5 + blockLength, data2.length);
}
result = exchangeCheck(apdu, acceptedSW);
offset += blockLength;
}
return result;
}
public void verifyPin(byte[] pin) throws BTChipException {
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_VERIFY_PIN, (byte)0x00, (byte)0x00, pin, OK);
}
public int getVerifyPinRemainingAttempts() throws BTChipException {
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_VERIFY_PIN, (byte)0x80, (byte)0x00, DUMMY, null);
if ((lastSW & 0xfff0) != 0x63c0) {
throw new BTChipException("Invalid status", lastSW);
}
return (lastSW - 0x63c0);
}
public BTChipPublicKey getWalletPublicKey(String keyPath) throws BTChipException {
byte data[] = BIP32Utils.splitPath(keyPath);
byte response[] = exchangeApdu(BTCHIP_CLA, BTCHIP_INS_GET_WALLET_PUBLIC_KEY, (byte)0x00, (byte)0x00, data, OK);
int offset = 0;
byte publicKey[] = new byte[response[offset]];
offset++;
System.arraycopy(response, offset, publicKey, 0, publicKey.length);
offset += publicKey.length;
byte address[] = new byte[response[offset]];
offset++;
System.arraycopy(response, offset, address, 0, address.length);
offset += address.length;
byte chainCode[] = new byte[32];
System.arraycopy(response, offset, chainCode, 0, chainCode.length);
return new BTChipPublicKey(publicKey, new String(address), chainCode);
}
public BTChipInput getTrustedInput(BitcoinTransaction transaction, long index, long sequence) throws BTChipException {
ByteArrayOutputStream data = new ByteArrayOutputStream();
// Header
BufferUtils.writeUint32BE(data, index);
BufferUtils.writeBuffer(data, transaction.getVersion());
VarintUtils.write(data, transaction.getInputs().size());
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_GET_TRUSTED_INPUT, (byte)0x00, (byte)0x00, data.toByteArray(), OK);
// Each input
for (BitcoinTransaction.BitcoinInput input : transaction.getInputs()) {
data = new ByteArrayOutputStream();
BufferUtils.writeBuffer(data, input.getPrevOut());
VarintUtils.write(data, input.getScript().length);
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_GET_TRUSTED_INPUT, (byte)0x80, (byte)0x00, data.toByteArray(), OK);
data = new ByteArrayOutputStream();
BufferUtils.writeBuffer(data, input.getScript());
exchangeApduSplit2(BTCHIP_CLA, BTCHIP_INS_GET_TRUSTED_INPUT, (byte)0x80, (byte)0x00, data.toByteArray(), input.getSequence(), OK);
}
// Number of outputs
data = new ByteArrayOutputStream();
VarintUtils.write(data, transaction.getOutputs().size());
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_GET_TRUSTED_INPUT, (byte)0x80, (byte)0x00, data.toByteArray(), OK);
// Each output
for (BitcoinTransaction.BitcoinOutput output : transaction.getOutputs()) {
data = new ByteArrayOutputStream();
BufferUtils.writeBuffer(data, output.getAmount());
VarintUtils.write(data, output.getScript().length);
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_GET_TRUSTED_INPUT, (byte)0x80, (byte)0x00, data.toByteArray(), OK);
data = new ByteArrayOutputStream();
BufferUtils.writeBuffer(data, output.getScript());
exchangeApduSplit(BTCHIP_CLA, BTCHIP_INS_GET_TRUSTED_INPUT, (byte)0x80, (byte)0x00, data.toByteArray(), OK);
}
// Locktime
byte[] response = exchangeApdu(BTCHIP_CLA, BTCHIP_INS_GET_TRUSTED_INPUT, (byte)0x80, (byte)0x00, transaction.getLockTime(), OK);
ByteArrayOutputStream sequenceBuf = new ByteArrayOutputStream();
BufferUtils.writeUint32LE(sequenceBuf, sequence);
return new BTChipInput(response, sequenceBuf.toByteArray(), true, false);
}
public BTChipInput createInput(byte[] value, byte[] sequence, boolean trusted, boolean segwit) {
return new BTChipInput(value, sequence, trusted, segwit);
}
public void startUntrustedTransction(boolean newTransaction, long inputIndex, BTChipInput usedInputList[], byte[] redeemScript, boolean segwit) throws BTChipException {
// Start building a fake transaction with the passed inputs
ByteArrayOutputStream data = new ByteArrayOutputStream();
BufferUtils.writeBuffer(data, BitcoinTransaction.DEFAULT_VERSION);
VarintUtils.write(data, usedInputList.length);
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_HASH_INPUT_START, (byte)0x00, (newTransaction ? (segwit ? (byte)0x02 : (byte)0x00) : (byte)0x80), data.toByteArray(), OK);
// Loop for each input
long currentIndex = 0;
for (BTChipInput input : usedInputList) {
byte[] script = (currentIndex == inputIndex ? redeemScript : new byte[0]);
data = new ByteArrayOutputStream();
data.write(input.isSegwit() ? (byte)0x02 : input.isTrusted() ? (byte)0x01 : (byte)0x00);
if (input.isTrusted()) {
// other inputs have constant length
data.write(input.getValue().length);
}
BufferUtils.writeBuffer(data, input.getValue());
VarintUtils.write(data, script.length);
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_HASH_INPUT_START, (byte)0x80, (byte)0x00, data.toByteArray(), OK);
data = new ByteArrayOutputStream();
BufferUtils.writeBuffer(data, script);
BufferUtils.writeBuffer(data, input.getSequence());
exchangeApduSplit(BTCHIP_CLA, BTCHIP_INS_HASH_INPUT_START, (byte)0x80, (byte)0x00, data.toByteArray(), OK);
currentIndex++;
}
}
private BTChipOutput convertResponseToOutput(byte[] response) throws BTChipException {
BTChipOutput result = null;
byte[] value = new byte[response[0] & 0xff];
System.arraycopy(response, 1, value, 0, value.length);
byte userConfirmationValue = response[1 + value.length];
if (userConfirmationValue == UserConfirmation.NONE.getValue()) {
result = new BTChipOutput(value, UserConfirmation.NONE);
}
else
if (userConfirmationValue == UserConfirmation.KEYBOARD.getValue()) {
result = new BTChipOutput(value, UserConfirmation.KEYBOARD);
}
else
if (userConfirmationValue == UserConfirmation.KEYCARD_DEPRECATED.getValue()) {
byte[] keycardIndexes = new byte[response.length - 2 - value.length];
System.arraycopy(response, 2 + value.length, keycardIndexes, 0, keycardIndexes.length);
result = new BTChipOutputKeycard(value, UserConfirmation.KEYCARD_DEPRECATED, keycardIndexes);
}
else
if (userConfirmationValue == UserConfirmation.KEYCARD.getValue()) {
byte keycardIndexesLength = response[2 + value.length];
byte[] keycardIndexes = new byte[keycardIndexesLength];
System.arraycopy(response, 3 + value.length, keycardIndexes, 0, keycardIndexes.length);
result = new BTChipOutputKeycard(value, UserConfirmation.KEYCARD, keycardIndexes);
}
else
if (userConfirmationValue == UserConfirmation.KEYCARD_NFC.getValue()) {
byte keycardIndexesLength = response[2 + value.length];
byte[] keycardIndexes = new byte[keycardIndexesLength];
System.arraycopy(response, 3 + value.length, keycardIndexes, 0, keycardIndexes.length);
result = new BTChipOutputKeycard(value, UserConfirmation.KEYCARD_NFC, keycardIndexes);
}
else
if (userConfirmationValue == UserConfirmation.KEYCARD_SCREEN.getValue()) {
byte keycardIndexesLength = response[2 + value.length];
byte[] keycardIndexes = new byte[keycardIndexesLength];
byte[] screenInfo = new byte[response.length - 3 - value.length - keycardIndexes.length];
System.arraycopy(response, 3 + value.length, keycardIndexes, 0, keycardIndexes.length);
System.arraycopy(response, 3 + value.length + keycardIndexes.length, screenInfo, 0, screenInfo.length);
result = new BTChipOutputKeycardScreen(value, UserConfirmation.KEYCARD_SCREEN, keycardIndexes, screenInfo);
}
if (result == null) {
throw new BTChipException("Unsupported user confirmation method");
}
return result;
}
public BTChipOutput finalizeInput(String outputAddress, String amount, String fees, String changePath) throws BTChipException {
BTChipOutput result;
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte path[] = BIP32Utils.splitPath(changePath);
data.write(outputAddress.length());
BufferUtils.writeBuffer(data, outputAddress.getBytes());
BufferUtils.writeUint64BE(data, CoinFormatUtils.toSatoshi(amount));
BufferUtils.writeUint64BE(data, CoinFormatUtils.toSatoshi(fees));
BufferUtils.writeBuffer(data, path);
byte[] response = exchangeApdu(BTCHIP_CLA, BTCHIP_INS_HASH_INPUT_FINALIZE, (byte)0x02, (byte)0x00, data.toByteArray(), OK);
result = convertResponseToOutput(response);
return result;
}
public BTChipOutput finalizeInputFull(byte[] data, String changePath, boolean skipChangeCheck) throws BTChipException {
BTChipOutput result = null;
int offset = 0;
byte[] response = null;
byte[] path;
boolean oldAPI = false;
if (!skipChangeCheck) {
if (changePath != null) {
path = BIP32Utils.splitPath(changePath);
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_HASH_INPUT_FINALIZE_FULL, (byte)0xFF, (byte)0x00, path, null);
oldAPI = ((lastSW == SW_INCORRECT_P1_P2) || (lastSW == SW_WRONG_P1_P2));
}
else {
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_HASH_INPUT_FINALIZE_FULL, (byte)0xFF, (byte)0x00, new byte[1], null);
oldAPI = ((lastSW == SW_INCORRECT_P1_P2) || (lastSW == SW_WRONG_P1_P2));
}
}
while (offset < data.length) {
int blockLength = ((data.length - offset) > 255 ? 255 : data.length - offset);
byte[] apdu = new byte[blockLength + 5];
apdu[0] = BTCHIP_CLA;
apdu[1] = BTCHIP_INS_HASH_INPUT_FINALIZE_FULL;
apdu[2] = ((offset + blockLength) == data.length ? (byte)0x80 : (byte)0x00);
apdu[3] = (byte)0x00;
apdu[4] = (byte)(blockLength);
System.arraycopy(data, offset, apdu, 5, blockLength);
response = exchangeCheck(apdu, OK);
offset += blockLength;
}
if (oldAPI) {
byte value = response[0];
if (value == UserConfirmation.NONE.getValue()) {
result = new BTChipOutput(new byte[0], UserConfirmation.NONE);
}
else
if (value == UserConfirmation.KEYBOARD.getValue()) {
result = new BTChipOutput(new byte[0], UserConfirmation.KEYBOARD);
}
}
else {
result = convertResponseToOutput(response);
}
if (result == null) {
throw new BTChipException("Unsupported user confirmation method");
}
return result;
}
public BTChipOutput finalizeInputFull(byte[] data) throws BTChipException {
return finalizeInputFull(data, null, false);
}
public BTChipOutput finalizeInputFull(byte[] data, String changePath) throws BTChipException {
return finalizeInputFull(data, changePath, false);
}
public BTChipOutput finalizeInput(byte[] outputScript, String outputAddress, String amount, String fees, String changePath) throws BTChipException {
// Try the new API first
boolean oldAPI;
byte[] path;
if (changePath != null) {
path = BIP32Utils.splitPath(changePath);
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_HASH_INPUT_FINALIZE_FULL, (byte)0xFF, (byte)0x00, path, null);
oldAPI = ((lastSW == SW_INCORRECT_P1_P2) || (lastSW == SW_WRONG_P1_P2));
}
else {
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_HASH_INPUT_FINALIZE_FULL, (byte)0xFF, (byte)0x00, new byte[1], null);
oldAPI = ((lastSW == SW_INCORRECT_P1_P2) || (lastSW == SW_WRONG_P1_P2));
}
if (oldAPI) {
return finalizeInput(outputAddress, amount, fees, changePath);
}
else {
return finalizeInputFull(outputScript, null, true);
}
}
public byte[] untrustedHashSign(String privateKeyPath, String pin, long lockTime, byte sigHashType) throws BTChipException {
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte path[] = BIP32Utils.splitPath(privateKeyPath);
BufferUtils.writeBuffer(data, path);
data.write(pin.length());
BufferUtils.writeBuffer(data, pin.getBytes());
BufferUtils.writeUint32BE(data, lockTime);
data.write(sigHashType);
byte[] response = exchangeApdu(BTCHIP_CLA, BTCHIP_INS_HASH_SIGN, (byte)0x00, (byte)0x00, data.toByteArray(), OK);
response[0] = (byte)0x30;
return response;
}
public byte[] untrustedHashSign(String privateKeyPath, String pin) throws BTChipException {
return untrustedHashSign(privateKeyPath, pin, 0, (byte)0x01);
}
public boolean shouldUseNewSigningApi() {
try {
if (this.firmwareVersion == null) this.getFirmwareVersion();
} catch (BTChipException e) {
return false;
}
if (this.firmwareVersion.getMajor() > 0x2001) { // 0x2001 = Ledger 1.x, 0x3001 = Nano S
return true;
} else if (this.firmwareVersion.getMajor() == 0x2001 &&
this.firmwareVersion.getMinor() > 0) {
return true;
} else if (this.firmwareVersion.getMajor() == 0x2001 &&
this.firmwareVersion.getMinor() == 0 &&
this.firmwareVersion.getPatch() >= 2) {
return true;
} else {
return false;
}
}
public boolean signMessagePrepare(String path, byte[] message) throws BTChipException {
ByteArrayOutputStream data = new ByteArrayOutputStream();
BufferUtils.writeBuffer(data, BIP32Utils.splitPath(path));
if (this.shouldUseNewSigningApi()) {
// length has two bytes in Ledger 1.0.2+
data.write((byte)0);
}
data.write((byte)message.length);
BufferUtils.writeBuffer(data, message);
final byte p2 = (byte) (this.shouldUseNewSigningApi() ? 0x01 : 0x00);
byte[] response = exchangeApdu(BTCHIP_CLA, BTCHIP_INS_SIGN_MESSAGE, (byte)0x00, p2, data.toByteArray(), OK);
return (response[0] == (byte)0x01);
}
public BTChipSignature signMessageSign(byte[] pin) throws BTChipException {
ByteArrayOutputStream data = new ByteArrayOutputStream();
if (pin == null) {
data.write((byte)0);
}
else {
data.write((byte)pin.length);
BufferUtils.writeBuffer(data, pin);
}
final byte p2 = (byte) (this.shouldUseNewSigningApi() ? 0x01 : 0x00);
byte[] response = exchangeApdu(BTCHIP_CLA, BTCHIP_INS_SIGN_MESSAGE, (byte)0x80, p2, data.toByteArray(), OK);
int yParity = (response[0] & 0x0F);
response[0] = (byte)0x30;
return new BTChipSignature(response, yParity);
}
public BTChipFirmware getFirmwareVersion() throws BTChipException {
byte[] response = exchangeApdu(BTCHIP_CLA, BTCHIP_INS_GET_FIRMWARE_VERSION, (byte)0x00, (byte)0x00, 0x00, OK);
boolean compressedKeys = (response[0] == (byte)0x01);
int major = ((response[1] & 0xff) << 8) | response[2] & 0xff;
int minor = response[3] & 0xff;
int patch = response[4] & 0xff;
this.firmwareVersion = new BTChipFirmware(major, minor, patch, compressedKeys);
return this.firmwareVersion;
}
public void setKeymapEncoding(byte[] keymapEncoding) throws BTChipException {
ByteArrayOutputStream data = new ByteArrayOutputStream();
BufferUtils.writeBuffer(data, keymapEncoding);
exchangeApdu(BTCHIP_CLA, BTCHIP_INS_SET_KEYMAP, (byte)0x00, (byte)0x00, data.toByteArray(), OK);
}
public boolean setup(OperationMode supportedOperationModes[], Feature features[], int keyVersion, int keyVersionP2SH, byte[] userPin, byte[] wipePin, byte[] keymapEncoding, byte[] seed, byte[] developerKey) throws BTChipException {
int operationModeFlags = 0;
int featuresFlags = 0;
ByteArrayOutputStream data = new ByteArrayOutputStream();
for (OperationMode currentOperationMode : supportedOperationModes) {
operationModeFlags |= currentOperationMode.getValue();
}
for (Feature currentFeature : features) {
featuresFlags |= currentFeature.getValue();
}
data.write(operationModeFlags);
data.write(featuresFlags);
data.write(keyVersion);
data.write(keyVersionP2SH);
if ((userPin.length < 0x04) || (userPin.length > 0x20)) {
throw new BTChipException("Invalid user PIN length");
}
data.write(userPin.length);
BufferUtils.writeBuffer(data, userPin);
if (wipePin != null) {
if (wipePin.length > 0x04) {
throw new BTChipException("Invalid wipe PIN length");
}
data.write(wipePin.length);
BufferUtils.writeBuffer(data, wipePin);
}
else {
data.write(0);
}
if (seed != null) {
if ((seed.length < 32) || (seed.length > 64)) {
throw new BTChipException("Invalid seed length");
}
data.write(seed.length);
BufferUtils.writeBuffer(data, seed);
}
else {
data.write(0);
}
if (developerKey != null) {
if (developerKey.length != 0x10) {
throw new BTChipException("Invalid developer key");
}
data.write(developerKey.length);
BufferUtils.writeBuffer(data, developerKey);
}
else {
data.write(0);
}
byte[] response = exchangeApdu(BTCHIP_CLA, BTCHIP_INS_SETUP, (byte)0x00, (byte)0x00, data.toByteArray(), OK);
if (keymapEncoding != null) {
setKeymapEncoding(keymapEncoding);
}
return (response[0] == (byte)0x01);
}
}