/* ******************************************************************************* * BTChip Bitcoin Hardware Wallet Java Card implementation * (c) 2013 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn * * Changes by Toporin for the Bitcoin SatoChip Hardware Wallet * Sources available on https://github.com/Toporin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************* */ package org.satochip.applet; import javacard.framework.JCSystem; import javacard.framework.Util; import javacard.security.MessageDigest; /** * Bitcoin transaction parsing * @author BTChip * */ public class Transaction { public static void init() { h = JCSystem.makeTransientShortArray((short)2, JCSystem.CLEAR_ON_DESELECT); ctx = JCSystem.makeTransientByteArray(TX_CONTEXT_SIZE, JCSystem.CLEAR_ON_DESELECT); digestFull = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); } private static void consumeTransaction(byte buffer[], short length) { digestFull.update(buffer, h[CURRENT], length); h[REMAINING] -= length; h[CURRENT] += length; } private static boolean parseVarint(byte[] buffer, byte[] target, short targetOffset) { if (h[REMAINING] < (short)1) { return false; } short firstByte = (short)(buffer[h[CURRENT]] & 0xff); if (firstByte < (short)0xfd) { Biginteger.setByte(target, targetOffset, (short)4, (byte)firstByte); consumeTransaction(buffer, (short)1); } else if (firstByte == (short)0xfd) { consumeTransaction(buffer, (short)1); if (h[REMAINING] < (short)2) { return false; } target[targetOffset]=0x00; target[(short)(targetOffset+1)]=0x00; target[(short)(targetOffset+2)]=buffer[(short)(h[CURRENT] + 1)]; target[(short)(targetOffset+3)]=buffer[h[CURRENT]]; consumeTransaction(buffer, (short)2); } else if (firstByte == (short)0xfe) { consumeTransaction(buffer, (short)1); if (h[REMAINING] < (short)4) { return false; } target[targetOffset]=buffer[(short)(h[CURRENT] + 3)]; target[(short)(targetOffset+1)]=buffer[(short)(h[CURRENT] + 2)]; target[(short)(targetOffset+2)]=buffer[(short)(h[CURRENT] + 1)]; target[(short)(targetOffset+3)]=buffer[h[CURRENT]]; consumeTransaction(buffer, (short)4); } else { return false; } return true; } public static void resetTransaction(){ ctx[TX_B_TRANSACTION_STATE] = STATE_NONE; Biginteger.setZero(ctx, TX_I_REMAINING_I, (short)4); Biginteger.setZero(ctx, TX_I_REMAINING_O, (short)4); Biginteger.setZero(ctx, TX_I_CURRENT_I, (short)4); Biginteger.setZero(ctx, TX_I_CURRENT_O, (short)4); Biginteger.setZero(ctx, TX_I_SCRIPT_REMAINING, (short)4); Biginteger.setZero(ctx, TX_A_TRANSACTION_AMOUNT, (short)8); Biginteger.setZero(ctx, TX_I_SCRIPT_COORD, (short)4); Biginteger.setZero(ctx, TX_TMP_BUFFER, (short)8); ctx[TX_I_SCRIPT_ACTIVE] = INACTIVE; digestFull.reset(); return; } public static byte parseTransaction(byte buffer[], short offset, short remaining) { h[CURRENT] = offset; h[REMAINING] = remaining; for (;;) { if (ctx[TX_B_TRANSACTION_STATE] == STATE_NONE) { // Parse the beginning of the transaction // Version if (h[REMAINING] < (short)4) { return RESULT_ERROR; } consumeTransaction(buffer, (short)4); // Number of inputs if (!parseVarint(buffer, ctx, TX_I_REMAINING_I)) { return RESULT_ERROR; } ctx[TX_B_TRANSACTION_STATE] = STATE_DEFINED_WAIT_INPUT; } if (ctx[TX_B_TRANSACTION_STATE] == STATE_DEFINED_WAIT_INPUT) { if (Biginteger.equalZero(ctx, TX_I_REMAINING_I,(short)4)) { if (ctx[TX_I_SCRIPT_ACTIVE]== INACTIVE){ // there should be exactly one input script active at this point return RESULT_ERROR; } // No more inputs to hash, move forward ctx[TX_B_TRANSACTION_STATE] = STATE_INPUT_HASHING_DONE; continue; } if (h[REMAINING] < (short)1) { // No more data to read, ok return RESULT_MORE; } // Proceed with the next input if (h[REMAINING] < (short)36) { // prevout : 32 hash + 4 index return RESULT_ERROR; } consumeTransaction(buffer, (short)36); // Read the script length if (!parseVarint(buffer, ctx, TX_I_SCRIPT_REMAINING)) { return RESULT_ERROR; } else if (!Biginteger.equalZero(ctx,TX_I_SCRIPT_REMAINING, (short)4)){ // check if a script was already present if (ctx[TX_I_SCRIPT_ACTIVE]== INACTIVE){ ctx[TX_I_SCRIPT_ACTIVE]= ACTIVE; Util.arrayCopyNonAtomic(ctx, TX_I_CURRENT_I, ctx, TX_I_SCRIPT_COORD, SIZEOF_U32); } else { // there should be only one input script active return RESULT_ERROR; } } ctx[TX_B_TRANSACTION_STATE] = STATE_INPUT_HASHING_IN_PROGRESS_INPUT_SCRIPT; } if (ctx[TX_B_TRANSACTION_STATE] == STATE_INPUT_HASHING_IN_PROGRESS_INPUT_SCRIPT) { if (h[REMAINING] < (short)1) { // No more data to read, ok return RESULT_MORE; } // if script size is zero or script is already consumed if (Biginteger.equalZero(ctx,TX_I_SCRIPT_REMAINING,(short)4)) { // Sequence if (h[REMAINING] < (short)4) { return RESULT_ERROR; } // TODO : enforce sequence consumeTransaction(buffer, (short)4); // Move to next input Biginteger.subtract1_carry(ctx, TX_I_REMAINING_I,(short)4); Biginteger.add1_carry(ctx, TX_I_CURRENT_I, (short)4); ctx[TX_B_TRANSACTION_STATE] = STATE_DEFINED_WAIT_INPUT; continue; } short scriptRemaining = Biginteger.getLSB(ctx, TX_I_SCRIPT_REMAINING,(short)4); short dataAvailable = (h[REMAINING] > scriptRemaining ? scriptRemaining : h[REMAINING]); if (dataAvailable == 0) { return RESULT_MORE; } consumeTransaction(buffer, dataAvailable); Biginteger.setByte(ctx, TX_TMP_BUFFER, (short)4, (byte)dataAvailable); Biginteger.subtract(ctx, TX_I_SCRIPT_REMAINING, ctx, TX_TMP_BUFFER, (short)4); // at this point the program loop until either the script or the buffer is consumed } if (ctx[TX_B_TRANSACTION_STATE] == STATE_INPUT_HASHING_DONE) { if (h[REMAINING] < (short)1) { // No more data to read, ok return RESULT_MORE; } // Number of outputs if (!parseVarint(buffer, ctx, TX_I_REMAINING_O)) { return RESULT_ERROR; } ctx[TX_B_TRANSACTION_STATE] = STATE_DEFINED_WAIT_OUTPUT; } if (ctx[TX_B_TRANSACTION_STATE] == STATE_DEFINED_WAIT_OUTPUT) { if (Biginteger.equalZero(ctx, TX_I_REMAINING_O,(short)4)) { // No more outputs to hash, move forward ctx[TX_B_TRANSACTION_STATE] = STATE_OUTPUT_HASHING_DONE; continue; } if (h[REMAINING] < (short)1) { // No more data to read, ok return RESULT_MORE; } // Amount if (h[REMAINING] < (short)8) { return RESULT_ERROR; } Biginteger.swap(buffer, h[CURRENT], ctx, TX_TMP_BUFFER, (short)8); Biginteger.add_carry(ctx, TX_A_TRANSACTION_AMOUNT, ctx, TX_TMP_BUFFER, (short)8); consumeTransaction(buffer, (short)8); // Read the script length if (!parseVarint(buffer, ctx, TX_I_SCRIPT_REMAINING)) { return RESULT_ERROR; } ctx[TX_B_TRANSACTION_STATE] = STATE_OUTPUT_HASHING_IN_PROGRESS_OUTPUT_SCRIPT; } if (ctx[TX_B_TRANSACTION_STATE] == STATE_OUTPUT_HASHING_IN_PROGRESS_OUTPUT_SCRIPT) { if (h[REMAINING] < (short)1) { // No more data to read, ok return RESULT_MORE; } if (Biginteger.equalZero(ctx,TX_I_SCRIPT_REMAINING, (short)4)) { // Move to next output Biginteger.subtract1_carry(ctx, TX_I_REMAINING_O, (short)4); Biginteger.add1_carry(ctx, TX_I_CURRENT_O, (short)4); ctx[TX_B_TRANSACTION_STATE] = STATE_DEFINED_WAIT_OUTPUT; continue; } short scriptRemaining = Biginteger.getLSB(ctx, TX_I_SCRIPT_REMAINING,(short)4); short dataAvailable = (h[REMAINING] > scriptRemaining ? scriptRemaining : h[REMAINING]); if (dataAvailable == 0) { return RESULT_MORE; } consumeTransaction(buffer, dataAvailable); Biginteger.setByte(ctx, TX_TMP_BUFFER, (short)4, (byte)dataAvailable); Biginteger.subtract(ctx, TX_I_SCRIPT_REMAINING, ctx, TX_TMP_BUFFER,(short)4); } if (ctx[TX_B_TRANSACTION_STATE] == STATE_OUTPUT_HASHING_DONE) { if (h[REMAINING] < (short)1) { // No more data to read, ok return RESULT_MORE; } // Locktime if (h[REMAINING] < (short)4) { return RESULT_ERROR; } consumeTransaction(buffer, (short)4); // sighash if (h[REMAINING] < (short)1) { // No more data to read, ok return RESULT_MORE; } if (h[REMAINING] < (short)4) { return RESULT_ERROR; } consumeTransaction(buffer, (short)4); ctx[TX_B_TRANSACTION_STATE] = STATE_PARSED; return RESULT_FINISHED; } } } private static short[] h; protected static byte[] ctx; public static MessageDigest digestFull; private static final byte CURRENT = (byte)0; private static final byte REMAINING = (byte)1; public static final byte STATE_NONE = (byte)0x00; public static final byte STATE_DEFINED_WAIT_INPUT = (byte)0x01; public static final byte STATE_INPUT_HASHING_IN_PROGRESS_INPUT_SCRIPT = (byte)0x02; public static final byte STATE_INPUT_HASHING_DONE = (byte)0x03; public static final byte STATE_DEFINED_WAIT_OUTPUT = (byte)0x04; public static final byte STATE_OUTPUT_HASHING_IN_PROGRESS_OUTPUT_SCRIPT = (byte)0x05; public static final byte STATE_OUTPUT_HASHING_DONE = (byte)0x06; public static final byte STATE_PARSED = (byte)0x07; public static final byte RESULT_FINISHED = (byte)0x13; public static final byte RESULT_ERROR = (byte)0x79; public static final byte RESULT_MORE = (byte)0x00; // Transaction context protected static final byte SIZEOF_U32 = 4; protected static final byte SIZEOF_U8 = 1; protected static final byte SIZEOF_AMOUNT = 8; protected static final byte INACTIVE = (byte)0x00; protected static final byte ACTIVE = (byte)0x01; // context data protected static final short TX_B_HASH_OPTION = (short)0; protected static final short TX_I_REMAINING_I = (short)(TX_B_HASH_OPTION + SIZEOF_U8); protected static final short TX_I_CURRENT_I = (short)(TX_I_REMAINING_I + SIZEOF_U32); protected static final short TX_I_REMAINING_O = (short)(TX_I_CURRENT_I + SIZEOF_U32); protected static final short TX_I_CURRENT_O = (short)(TX_I_REMAINING_O + SIZEOF_U32); protected static final short TX_I_SCRIPT_REMAINING = (short)(TX_I_CURRENT_O + SIZEOF_U32); protected static final short TX_B_TRANSACTION_STATE = (short)(TX_I_SCRIPT_REMAINING + SIZEOF_U32); protected static final short TX_A_TRANSACTION_AMOUNT = (short)(TX_B_TRANSACTION_STATE + SIZEOF_U8); protected static final short TX_I_SCRIPT_ACTIVE = (short)(TX_A_TRANSACTION_AMOUNT + SIZEOF_AMOUNT); protected static final short TX_I_SCRIPT_COORD = (short)(TX_I_SCRIPT_ACTIVE + SIZEOF_U8); protected static final short TX_TMP_BUFFER = (short)(TX_I_SCRIPT_COORD + SIZEOF_U32); protected static final short TX_CONTEXT_SIZE = (short)(TX_TMP_BUFFER + SIZEOF_AMOUNT); }