/** Writes 4 byte wallet version. This is in its own function because * it's used by both newWallet() and changeEncryptionKey(). * \return See #NonVolatileReturnEnum. */ static NonVolatileReturn writeWalletVersion(void) { uint8_t buffer[4]; if (isEncryptionKeyNonZero()) { writeU32LittleEndian(buffer, VERSION_IS_ENCRYPTED); } else { writeU32LittleEndian(buffer, VERSION_UNENCRYPTED); } return nonVolatileWrite(buffer, OFFSET_VERSION, 4); }
/** Wrapper around nonVolatileRead() which also decrypts data * using xexDecrypt(). Because this uses encryption, it is much slower * than nonVolatileRead(). The parameters and return values are identical * to that of nonVolatileRead(). * \param data A pointer to the buffer which will receive the data. * \param address Byte offset specifying where in non-volatile storage to * start reading from. * \param length The number of bytes to read. * \return See #NonVolatileReturnEnum for return values. */ NonVolatileReturn encryptedNonVolatileRead(uint8_t *data, uint32_t address, uint8_t length) { uint32_t block_start; uint32_t block_end; uint8_t block_offset; uint8_t ciphertext[16]; uint8_t plaintext[16]; uint8_t n[16]; NonVolatileReturn r; block_start = address & 0xfffffff0; block_offset = (uint8_t)(address & 0x0000000f); block_end = (address + length - 1) & 0xfffffff0; memset(n, 0, 16); for (; block_start <= block_end; block_start += 16) { r = nonVolatileRead(ciphertext, block_start, 16); if (r != NV_NO_ERROR) { return r; } writeU32LittleEndian(n, block_start); xexDecrypt(plaintext, ciphertext, n, 1, nv_storage_tweak_key, nv_storage_encrypt_key); while (length && block_offset < 16) { *data++ = plaintext[block_offset++]; length--; } block_offset = 0; } return NV_NO_ERROR; }
/** Generate a new address using the deterministic private key generator. * \param out_address The new address will be written here (if everything * goes well). This must be a byte array with space for * 20 bytes. * \param out_public_key The public key corresponding to the new address will * be written here (if everything goes well). * \return The address handle of the new address on success, * or #BAD_ADDRESS_HANDLE if an error occurred. * Use walletGetLastError() to get more detail about an error. */ AddressHandle makeNewAddress(uint8_t *out_address, PointAffine *out_public_key) { uint8_t buffer[4]; if (!wallet_loaded) { last_error = WALLET_NOT_THERE; return BAD_ADDRESS_HANDLE; } #ifdef TEST_WALLET if (num_addresses == MAX_TESTING_ADDRESSES) #else if (num_addresses == MAX_ADDRESSES) #endif // #ifdef TEST_WALLET { last_error = WALLET_FULL; return BAD_ADDRESS_HANDLE; } num_addresses++; writeU32LittleEndian(buffer, num_addresses); if (encryptedNonVolatileWrite(buffer, OFFSET_NUM_ADDRESSES, 4) != NV_NO_ERROR) { last_error = WALLET_WRITE_ERROR; return BAD_ADDRESS_HANDLE; } last_error = getAddressAndPublicKey(out_address, out_public_key, num_addresses); if (last_error != WALLET_NO_ERROR) { return BAD_ADDRESS_HANDLE; } else { return num_addresses; } }
// Get a byte from the serial link, sending an acknowledgement if required. static uint8_t receiveByte(void) { uint8_t ack_buffer[5]; uint8_t buffer; read(fd_serial, &buffer, 1); rx_bytes_to_ack--; if (!rx_bytes_to_ack) { rx_bytes_to_ack = RX_ACKNOWLEDGE_INTERVAL; ack_buffer[0] = 0xff; writeU32LittleEndian(&(ack_buffer[1]), rx_bytes_to_ack); write(fd_serial, ack_buffer, 5); } return buffer; }
/** Translates a return value from one of the wallet functions into a response * packet to be written to the stream. If the wallet return value indicates * success, a payload can be included with the packet. Otherwise, if the * wallet return value indicates failure, the payload is a text error message * describing how the wallet function failed. * \param r The return value from the wallet function. * \param length The length of the success payload (use 0 for no payload) in * number of bytes. * \param data A byte array holding the data of the success payload. * Use #NULL for no payload. */ static void translateWalletError(WalletErrors r, uint8_t length, uint8_t *data) { uint8_t buffer[4]; if (r == WALLET_NO_ERROR) { streamPutOneByte(PACKET_TYPE_SUCCESS); // type writeU32LittleEndian(buffer, length); writeBytesToStream(buffer, 4); // length writeBytesToStream(data, length); // value } else { writeString(STRINGSET_WALLET, (uint8_t)r, PACKET_TYPE_FAILURE); } }
/** Sends a packet with a string as payload. * \param set See getString(). * \param spec See getString(). * \param command The type of the packet, as defined in the file PROTOCOL. */ static void writeString(StringSet set, uint8_t spec, uint8_t command) { uint8_t buffer[4]; uint8_t one_char; uint16_t length; uint16_t i; streamPutOneByte(command); // type length = getStringLength(set, spec); writeU32LittleEndian(buffer, length); writeBytesToStream(buffer, 4); // length for (i = 0; i < length; i++) { one_char = (uint8_t)getString(set, spec, i); streamPutOneByte(one_char); // value } }
/** Wrapper around nonVolatileWrite() which also encrypts data * using xexEncrypt(). Because this uses encryption, it is much slower * than nonVolatileWrite(). The parameters and return values are identical * to that of nonVolatileWrite(). * \param data A pointer to the data to be written. * \param partition The partition to write to. Must be one of #NVPartitions. * \param address Byte offset specifying where in the partition to * start writing to. * \param length The number of bytes to write. * \return See #NonVolatileReturnEnum for return values. * \warning Writes may be buffered; use nonVolatileFlush() to be sure that * data is actually written to non-volatile storage. */ NonVolatileReturn encryptedNonVolatileWrite(uint8_t *data, NVPartitions partition, uint32_t address, uint32_t length) { uint32_t block_start; uint32_t block_end; uint8_t block_offset; uint8_t ciphertext[16]; uint8_t plaintext[16]; uint8_t n[16]; NonVolatileReturn r; block_start = address & 0xfffffff0; block_offset = (uint8_t)(address & 0x0000000f); block_end = (address + length - 1) & 0xfffffff0; if ((address + length) < address) { // Overflow occurred. return NV_INVALID_ADDRESS; } memset(n, 0, 16); for (; block_start <= block_end; block_start += 16) { r = nonVolatileRead(ciphertext, partition, block_start, 16); if (r != NV_NO_ERROR) { return r; } writeU32LittleEndian(n, block_start); xexDecrypt(plaintext, ciphertext, n, 1); while (length && block_offset < 16) { plaintext[block_offset++] = *data++; length--; } block_offset = 0; xexEncrypt(ciphertext, plaintext, n, 1); r = nonVolatileWrite(ciphertext, partition, block_start, 16); if (r != NV_NO_ERROR) { return r; } } return NV_NO_ERROR; }
/** Send a packet containing a list of wallets. */ static NOINLINE void listWallets(void) { uint8_t version[4]; uint8_t name[NAME_LENGTH]; uint8_t buffer[4]; WalletErrors wallet_return; if (getWalletInfo(version, name) != WALLET_NO_ERROR) { wallet_return = walletGetLastError(); translateWalletError(wallet_return, 0, NULL); } else { streamPutOneByte(PACKET_TYPE_SUCCESS); // type writeU32LittleEndian(buffer, 4 + NAME_LENGTH); // length writeBytesToStream(buffer, 4); writeBytesToStream(version, 4); writeBytesToStream(name, NAME_LENGTH); } }
/** Create new wallet. A brand new wallet contains no addresses and should * have a unique, unpredictable deterministic private key generation seed. * \param name Should point to #NAME_LENGTH bytes (padded with spaces if * necessary) containing the desired name of the wallet. * \return #WALLET_NO_ERROR on success, or one of #WalletErrorsEnum if an * error occurred. If this returns #WALLET_NO_ERROR, then the * wallet will also be loaded. * \warning This will erase the current one. */ WalletErrors newWallet(uint8_t *name) { uint8_t buffer[32]; WalletErrors r; // Erase all traces of the existing wallet. r = sanitiseNonVolatileStorage(0, WALLET_RECORD_LENGTH); if (r != WALLET_NO_ERROR) { last_error = r; return last_error; } // Write version. if (writeWalletVersion() != NV_NO_ERROR) { last_error = WALLET_WRITE_ERROR; return last_error; } // Write reserved area 1. writeU32LittleEndian(buffer, 0); if (nonVolatileWrite(buffer, OFFSET_RESERVED1, 4) != NV_NO_ERROR) { last_error = WALLET_WRITE_ERROR; return last_error; } // Write name of wallet. if (nonVolatileWrite(name, OFFSET_NAME, NAME_LENGTH) != NV_NO_ERROR) { last_error = WALLET_WRITE_ERROR; return last_error; } // Write number of addresses. writeU32LittleEndian(buffer, 0); if (encryptedNonVolatileWrite(buffer, OFFSET_NUM_ADDRESSES, 4) != NV_NO_ERROR) { last_error = WALLET_WRITE_ERROR; return last_error; } // Write nonce 1. getRandom256(buffer); if (encryptedNonVolatileWrite(buffer, OFFSET_NONCE1, 8) != NV_NO_ERROR) { last_error = WALLET_WRITE_ERROR; return last_error; } // Write reserved area 2. writeU32LittleEndian(buffer, 0); if (encryptedNonVolatileWrite(buffer, OFFSET_RESERVED2, 4) != NV_NO_ERROR) { last_error = WALLET_WRITE_ERROR; return last_error; } // Write seed for deterministic address generator. getRandom256(buffer); if (encryptedNonVolatileWrite(buffer, OFFSET_SEED, 32) != NV_NO_ERROR) { last_error = WALLET_WRITE_ERROR; return last_error; } getRandom256(buffer); if (encryptedNonVolatileWrite(buffer, OFFSET_SEED + 32, 32) != NV_NO_ERROR) { last_error = WALLET_WRITE_ERROR; return last_error; } nonVolatileFlush(); // Write checksum. r = writeWalletChecksum(); if (r != WALLET_NO_ERROR) { last_error = r; return last_error; } nonVolatileFlush(); last_error = initWallet(); return last_error; }
/** Sanitise (clear) a selected area of non-volatile storage. This will clear * the area between start (inclusive) and end (exclusive). * \param start The first address which will be cleared. * \param end One byte past the last address which will be cleared. * \return #WALLET_NO_ERROR on success, or one of #WalletErrorsEnum if an * error occurred. This will still return #WALLET_NO_ERROR even if * end is an address beyond the end of the non-volatile storage area. * This is done so that using start = 0 and end = 0xffffffff will * clear the entire non-volatile storage area. * \warning start and end must be a multiple of 32 (unless start is 0 and * end is 0xffffffff). */ WalletErrors sanitiseNonVolatileStorage(uint32_t start, uint32_t end) { uint8_t buffer[32]; uint32_t address; NonVolatileReturn r; uint8_t pass; r = NV_NO_ERROR; for (pass = 0; pass < 4; pass++) { address = start; r = NV_NO_ERROR; while ((r == NV_NO_ERROR) && (address < end)) { if (pass == 0) { memset(buffer, 0, sizeof(buffer)); } else if (pass == 1) { memset(buffer, 0xff, sizeof(buffer)); } else { getRandom256(buffer); } r = nonVolatileWrite(buffer, address, 32); nonVolatileFlush(); address += 32; } if ((r != NV_INVALID_ADDRESS) && (r != NV_NO_ERROR)) { // Uh oh, probably an I/O error. break; } } // end for (pass = 0; pass < 4; pass++) if ((r == NV_INVALID_ADDRESS) || (r == NV_NO_ERROR)) { // Write VERSION_NOTHING_THERE to all possible locations of the // version field. This ensures that a wallet won't accidentally // (1 in 2 ^ 31 chance) be recognised as a valid wallet by // getWalletInfo(). writeU32LittleEndian(buffer, VERSION_NOTHING_THERE); r = nonVolatileWrite(buffer, OFFSET_VERSION, 4); if (r == NV_NO_ERROR) { last_error = WALLET_NO_ERROR; } else { last_error = WALLET_WRITE_ERROR; } } else { last_error = WALLET_WRITE_ERROR; } return last_error; }
/** Get packet from stream and deal with it. This basically implements the * protocol described in the file PROTOCOL. * * This function will always completely * read a packet before sending a response packet. As long as the host * does the same thing, deadlocks cannot occur. Thus a productive * communication session between the hardware Bitcoin wallet and a host * should consist of the wallet and host alternating between sending a * packet and receiving a packet. */ void processPacket(void) { uint8_t command; // Technically, the length of buffer should also be >= 4, since it is used // in a couple of places to obtain 32 bit values. This is guaranteed by // the reference to WALLET_ENCRYPTION_KEY_LENGTH, since no-one in their // right mind would use encryption with smaller than 32 bit keys. uint8_t buffer[MAX(NAME_LENGTH, WALLET_ENCRYPTION_KEY_LENGTH)]; uint32_t num_addresses; AddressHandle ah; WalletErrors wallet_return; command = streamGetOneByte(); getBytesFromStream(buffer, 4); payload_length = readU32LittleEndian(buffer); // Checklist for each case: // 1. Have you checked or dealt with length? // 2. Have you fully read the input stream before writing (to avoid // deadlocks)? // 3. Have you asked permission from the user (for potentially dangerous // operations)? // 4. Have you checked for errors from wallet functions? // 5. Have you used the right check for the wallet functions? switch (command) { case PACKET_TYPE_PING: // Ping request. // Just throw away the data and then send response. readAndIgnoreInput(); writeString(STRINGSET_MISC, MISCSTR_VERSION, PACKET_TYPE_PING_REPLY); break; // Commands PACKET_TYPE_PING_REPLY, PACKET_TYPE_SUCCESS and // PACKET_TYPE_FAILURE should never be received; they are only sent. case PACKET_TYPE_NEW_WALLET: // Create new wallet. if (!expectLength(WALLET_ENCRYPTION_KEY_LENGTH + NAME_LENGTH)) { getBytesFromStream(buffer, WALLET_ENCRYPTION_KEY_LENGTH); setEncryptionKey(buffer); getBytesFromStream(buffer, NAME_LENGTH); if (askUser(ASKUSER_NUKE_WALLET)) { writeString(STRINGSET_MISC, MISCSTR_PERMISSION_DENIED, PACKET_TYPE_FAILURE); } else { wallet_return = newWallet(buffer); translateWalletError(wallet_return, 0, NULL); } } break; case PACKET_TYPE_NEW_ADDRESS: // Create new address in wallet. if (!expectLength(0)) { if (askUser(ASKUSER_NEW_ADDRESS)) { writeString(STRINGSET_MISC, MISCSTR_PERMISSION_DENIED, PACKET_TYPE_FAILURE); } else { getAndSendAddressAndPublicKey(1); } } break; case PACKET_TYPE_GET_NUM_ADDRESSES: // Get number of addresses in wallet. if (!expectLength(0)) { num_addresses = getNumAddresses(); writeU32LittleEndian(buffer, num_addresses); wallet_return = walletGetLastError(); translateWalletError(wallet_return, 4, buffer); } break; case PACKET_TYPE_GET_ADDRESS_PUBKEY: // Get address and public key corresponding to an address handle. if (!expectLength(4)) { getAndSendAddressAndPublicKey(0); } break; case PACKET_TYPE_SIGN_TRANSACTION: // Sign a transaction. if (payload_length <= 4) { readAndIgnoreInput(); writeString(STRINGSET_MISC, MISCSTR_INVALID_PACKET, PACKET_TYPE_FAILURE); } else { getBytesFromStream(buffer, 4); ah = readU32LittleEndian(buffer); // Don't need to subtract 4 off payload_length because // getBytesFromStream() has already done so. validateAndSignTransaction(ah, payload_length); payload_length = 0; } break; case PACKET_TYPE_LOAD_WALLET: // Load wallet. if (!expectLength(WALLET_ENCRYPTION_KEY_LENGTH)) { getBytesFromStream(buffer, WALLET_ENCRYPTION_KEY_LENGTH); setEncryptionKey(buffer); wallet_return = initWallet(); translateWalletError(wallet_return, 0, NULL); } break; case PACKET_TYPE_UNLOAD_WALLET: // Unload wallet. if (!expectLength(0)) { clearEncryptionKey(); sanitiseRam(); memset(buffer, 0xff, sizeof(buffer)); memset(buffer, 0, sizeof(buffer)); wallet_return = uninitWallet(); translateWalletError(wallet_return, 0, NULL); } break; case PACKET_TYPE_FORMAT: // Format storage. if (!expectLength(0)) { if (askUser(ASKUSER_FORMAT)) { writeString(STRINGSET_MISC, MISCSTR_PERMISSION_DENIED, PACKET_TYPE_FAILURE); } else { wallet_return = sanitiseNonVolatileStorage(0, 0xffffffff); translateWalletError(wallet_return, 0, NULL); uninitWallet(); // force wallet to unload } } break; case PACKET_TYPE_CHANGE_KEY: // Change wallet encryption key. if (!expectLength(WALLET_ENCRYPTION_KEY_LENGTH)) { getBytesFromStream(buffer, WALLET_ENCRYPTION_KEY_LENGTH); wallet_return = changeEncryptionKey(buffer); translateWalletError(wallet_return, 0, NULL); } break; case PACKET_TYPE_CHANGE_NAME: // Change wallet name. if (!expectLength(NAME_LENGTH)) { getBytesFromStream(buffer, NAME_LENGTH); if (askUser(ASKUSER_CHANGE_NAME)) { writeString(STRINGSET_MISC, MISCSTR_PERMISSION_DENIED, PACKET_TYPE_FAILURE); } else { wallet_return = changeWalletName(buffer); translateWalletError(wallet_return, 0, NULL); } } break; case PACKET_TYPE_LIST_WALLETS: // List wallets. if (!expectLength(0)) { listWallets(); } break; default: // Unknown command. readAndIgnoreInput(); writeString(STRINGSET_MISC, MISCSTR_INVALID_PACKET, PACKET_TYPE_FAILURE); break; } #ifdef TEST_STREAM_COMM assert(payload_length == 0); #endif }
/** Send a packet containing an address and its corresponding public key. * This can generate new addresses as well as obtain old addresses. Both * use cases were combined into one function because they involve similar * processes. * \param generate_new If this is non-zero, a new address will be generated * and the address handle of the generated address will * be prepended to the output packet. * If this is zero, the address handle will be read from * the input stream. No address handle will be prepended * to the output packet. */ static NOINLINE void getAndSendAddressAndPublicKey(uint8_t generate_new) { AddressHandle ah; PointAffine public_key; uint8_t address[20]; uint8_t buffer[4]; WalletErrors r; if (generate_new) { // Generate new address handle. r = WALLET_NO_ERROR; ah = makeNewAddress(address, &public_key); if (ah == BAD_ADDRESS_HANDLE) { r = walletGetLastError(); } } else { // Read address handle from input stream. getBytesFromStream(buffer, 4); ah = readU32LittleEndian(buffer); r = getAddressAndPublicKey(address, &public_key, ah); } if (r == WALLET_NO_ERROR) { streamPutOneByte(PACKET_TYPE_SUCCESS); // type if (generate_new) { // 4 (address handle) + 20 (address) + 65 (public key) writeU32LittleEndian(buffer, 89); } else { // 20 (address) + 65 (public key) writeU32LittleEndian(buffer, 85); } writeBytesToStream(buffer, 4); // length if (generate_new) { writeU32LittleEndian(buffer, ah); writeBytesToStream(buffer, 4); } writeBytesToStream(address, 20); // The format of public keys sent is compatible with // "SEC 1: Elliptic Curve Cryptography" by Certicom research, obtained // 15-August-2011 from: http://www.secg.org/collateral/sec1_final.pdf // section 2.3 ("Data Types and Conversions"). The document basically // says that integers should be represented big-endian and that a 0x04 // should be prepended to indicate that the public key is // uncompressed. streamPutOneByte(0x04); swapEndian256(public_key.x); swapEndian256(public_key.y); writeBytesToStream(public_key.x, 32); writeBytesToStream(public_key.y, 32); } else { translateWalletError(r, 0, NULL); } // end if (r == WALLET_NO_ERROR) }