/** Initialise wallet (load it if it's there). * \return #WALLET_NO_ERROR on success, or one of #WalletErrorsEnum if an * error occurred. */ WalletErrors initWallet(void) { uint8_t buffer[32]; uint8_t hash[32]; uint32_t version; wallet_loaded = 0; // Read version. if (nonVolatileRead(buffer, OFFSET_VERSION, 4) != NV_NO_ERROR) { last_error = WALLET_READ_ERROR; return last_error; } version = readU32LittleEndian(buffer); if ((version != VERSION_UNENCRYPTED) && (version != VERSION_IS_ENCRYPTED)) { last_error = WALLET_NOT_THERE; return last_error; } // Calculate checksum and check that it matches. if (calculateWalletChecksum(hash) != NV_NO_ERROR) { last_error = WALLET_READ_ERROR; return last_error; } if (encryptedNonVolatileRead(buffer, OFFSET_CHECKSUM, 32) != NV_NO_ERROR) { last_error = WALLET_READ_ERROR; return last_error; } if (bigCompare(buffer, hash) != BIGCMP_EQUAL) { last_error = WALLET_NOT_THERE; return last_error; } // Read number of addresses. if (encryptedNonVolatileRead(buffer, OFFSET_NUM_ADDRESSES, 4) != NV_NO_ERROR) { last_error = WALLET_READ_ERROR; return last_error; } num_addresses = readU32LittleEndian(buffer); wallet_loaded = 1; last_error = WALLET_NO_ERROR; return last_error; }
// Receive a packet, copying it into a memory buffer and returning the buffer static uint8_t *receivePacket(int fd) { uint8_t packet_header[5]; uint8_t *packet_buffer; uint32_t length; uint32_t i; for (i = 0; i < 5; i++) { packet_header[i] = receiveByte(fd); } length = readU32LittleEndian(&(packet_header[1])); if (length > PACKET_LENGTH_LIMIT) { printf("Got absurdly large packet length of %d\n", length); printf("Exiting, since the packet is probably garbled\n"); exit(1); } packet_buffer = malloc(length + 5); memcpy(packet_buffer, packet_header, 5); for (i = 0; i < length; i++) { packet_buffer[i + 5] = receiveByte(fd); } return packet_buffer; }
// Display packet contents on screen static void displayPacket(uint8_t *packet_data, uint32_t buffer_length) { uint8_t command; uint8_t one_byte; uint32_t length; uint32_t i; command = packet_data[0]; length = readU32LittleEndian(&(packet_data[1])); printf("command 0x%02x (%s)\n", command, packetCommandToText(command)); printf("Payload length: %d\n", length); // display hex bytes for (i = 0; i < length; i++) { if (i && !(i & 15)) { printf("\n"); } one_byte = packet_data[i + 5]; printf(" %02x", one_byte); if ((i + 5) >= buffer_length) { printf(" ***unexpected end of packet***"); break; } } printf("\n"); // display ASCII for (i = 0; i < length; i++) { if (i && !(i & 15)) { printf("\n"); } one_byte = packet_data[i + 5]; if ((one_byte < 32) || (one_byte > 126)) { printf("."); } else { printf("%c", packet_data[i + 5]); } if ((i + 5) >= buffer_length) { break; } } printf("\n"); }
// Receive real number from serial link. The real number should be in Q16.16 // representation. This is so that the device under test doesn't have to // do the conversion to floating-point. static double receiveDouble(void) { uint8_t buffer[4]; int j; for (j = 0; j < 4; j++) { buffer[j] = receiveByte(); } // The cast from uint32_t to fix16_t isn't platform-independent, because // the C99 specification doesn't make any guarantees about conversions // to signed integer types (...if the destination type cannot store the // source value, which will be the case if the fix16_t is negative). // But it should work on nearly every contemporary platform. return fix16_to_dbl((fix16_t)readU32LittleEndian(buffer)); }
// Send a byte to the serial link, waiting for acknowledgement if required. static void sendByte(uint8_t data) { uint8_t ack_buffer[5]; uint8_t buffer; buffer = data; write(fd_serial, &buffer, 1); tx_bytes_to_ack--; if (!tx_bytes_to_ack) { read(fd_serial, ack_buffer, 5); if (ack_buffer[0] != 0xff) { printf("Unexpected acknowledgement format (%d)\n", (int)ack_buffer[0]); printf("Exiting, since the serial link is probably dodgy\n"); exit(1); } tx_bytes_to_ack = readU32LittleEndian(&(ack_buffer[1])); } }
int main(int argc, char **argv) { int i; int matches; int succeeded; int failed; char *newline_position; char buffer[512]; int input_array[SAMPLE_COUNT]; double expected_array[OUTPUTS_TO_CHECK]; double output_array[OUTPUTS_TO_CHECK]; FILE *f_vectors; // file containing test vectors struct termios options; struct termios old_options; uint8_t cycles_buffer[4]; if (argc != 2) { printf("Usage: %s <serial device>\n", argv[0]); printf("\n"); printf("Example: %s /dev/ttyUSB0\n", argv[0]); exit(1); } // Attempt to open serial link. fd_serial = open(argv[1], O_RDWR | O_NOCTTY); if (fd_serial == -1) { printf("Could not open device \"%s\"\n", argv[1]); printf("Make sure you have permission to open it. In many systems, only\n"); printf("root can access devices by default.\n"); exit(1); } fcntl(fd_serial, F_SETFL, 0); // block on reads tcgetattr(fd_serial, &old_options); // save configuration memcpy(&options, &old_options, sizeof(options)); cfsetispeed(&options, B57600); // baud rate 57600 cfsetospeed(&options, B57600); options.c_cflag |= (CLOCAL | CREAD); // enable receiver and set local mode on options.c_cflag &= ~PARENB; // no parity options.c_cflag &= ~CSTOPB; // 1 stop bit options.c_cflag &= ~CSIZE; // character size mask options.c_cflag |= CS8; // 8 data bits options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw input options.c_lflag &= ~(XCASE | ECHOK | ECHONL | ECHOCTL | ECHOPRT | ECHOKE); // disable more stuff options.c_iflag &= ~(IXON | IXOFF | IXANY); // no software flow control options.c_iflag &= ~(INPCK | INLCR | IGNCR | ICRNL | IUCLC); // disable more stuff options.c_oflag &= ~OPOST; // raw output tcsetattr(fd_serial, TCSANOW, &options); rx_bytes_to_ack = DEFAULT_ACKNOWLEDGE_INTERVAL; tx_bytes_to_ack = DEFAULT_ACKNOWLEDGE_INTERVAL; // Attempt to open file containing test vectors. f_vectors = fopen("statistics_test_vectors.txt", "r"); if (f_vectors == NULL) { printf("Could not open \"statistics_test_vectors.txt\" for reading\n"); exit(1); } sendByte(0); // set device testing mode (see testStatistics() in ../hwrng.c) succeeded = 0; failed = 0; while (!feof(f_vectors)) { // Read name of test. fgets(buffer, sizeof(buffer), f_vectors); // Remove newlines. newline_position = strrchr(buffer, '\n'); if (newline_position != NULL) { *newline_position = '\0'; } newline_position = strrchr(buffer, '\r'); if (newline_position != NULL) { *newline_position = '\0'; } printf("%s: ", buffer); readIntegerArray(f_vectors, input_array, SAMPLE_COUNT); readRealArray(f_vectors, expected_array, OUTPUTS_TO_CHECK); sendIntegerArray(input_array, SAMPLE_COUNT); receiveRealArray(output_array, OUTPUTS_TO_CHECK); matches = realArraysEqualWithinTolerance(expected_array, output_array, OUTPUTS_TO_CHECK); // Get number of cycles required to do all tests. for (i = 0; i < 4; i++) { cycles_buffer[i] = receiveByte(); } printf("cycles = %u ", readU32LittleEndian(cycles_buffer)); if (matches) { printf("[pass]\n"); succeeded++; } else { printf("[fail]\n"); // Make failure noticable. printf("************************\n"); printf("FAIL FAIL FAIL FAIL FAIL\n"); printf("************************\n"); failed++; } } printf("Tests which succeeded: %d\n", succeeded); printf("Tests which failed: %d\n", failed); fclose(f_vectors); tcsetattr(fd_serial, TCSANOW, &old_options); // restore configuration close(fd_serial); exit(0); }
int main(int argc, char **argv) { char filename[256]; char *newline; int abort; FILE *file_to_send; int fd_serial; uint8_t *packet_buffer; long int size; long int i; struct termios options; if (argc != 2) { printf("Hardware BitCoin wallet tester\n"); printf("Usage: %s <serial device>\n", argv[0]); printf("\n"); printf("Example: %s /dev/ttyUSB0\n", argv[0]); exit(1); } // Attempt to open serial link. fd_serial = open(argv[1], O_RDWR | O_NOCTTY); if (fd_serial == -1) { printf("Could not open device \"%s\"\n", argv[1]); printf("Make sure you have permission to open it. In many systems, only\n"); printf("root can access devices by default.\n"); exit(1); } fcntl(fd_serial, F_SETFL, 0); // block on reads tcgetattr(fd_serial, &options); cfsetispeed(&options, B57600); // baud rate 57600 cfsetospeed(&options, B57600); options.c_cflag |= (CLOCAL | CREAD); // enable receiver and set local mode on options.c_cflag &= ~PARENB; // no parity options.c_cflag &= ~CSTOPB; // 1 stop bit options.c_cflag &= ~CSIZE; // character size mask options.c_cflag |= CS8; // 8 data bits options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw input options.c_iflag &= ~(IXON | IXOFF | IXANY); // no software flow control options.c_oflag &= ~OPOST; // raw output tcsetattr(fd_serial, TCSANOW, &options); tx_bytes_to_ack = DEFAULT_ACKNOWLEDGE_INTERVAL; rx_bytes_to_ack = DEFAULT_ACKNOWLEDGE_INTERVAL; abort = 0; do { // Get filename from user. printf("Enter file to send (blank to quit): "); fgets(filename, sizeof(filename), stdin); newline = strrchr(filename, '\r'); if (newline != NULL) { *newline = '\0'; } newline = strrchr(filename, '\n'); if (newline != NULL) { *newline = '\0'; } if (strcmp(filename, "")) { file_to_send = fopen(filename, "rb"); if (file_to_send == NULL) { printf("Couldn't open file \"%s\"\n", filename); } else { // Get file length then read entire contents of file. fseek(file_to_send, 0, SEEK_END); size = ftell(file_to_send); fseek(file_to_send, 0, SEEK_SET); packet_buffer = malloc(size); fread(packet_buffer, size, 1, file_to_send); fclose(file_to_send); printf("Sending packet: "); displayPacket(packet_buffer, size); // Send the packet. for (i = 0; i < size; i++) { sendByte(packet_buffer[i], fd_serial); } free(packet_buffer); // Get and display response packet. packet_buffer = receivePacket(fd_serial); size = 5 + readU32LittleEndian(&(packet_buffer[1])); printf("Received packet: "); displayPacket(packet_buffer, size); free(packet_buffer); } } else { abort = 1; } } while (!abort); close(fd_serial); exit(0); }
int main(void) { uint8_t temp[128]; uint8_t address1[20]; uint8_t address2[20]; uint8_t name[NAME_LENGTH]; uint8_t encryption_key[WALLET_ENCRYPTION_KEY_LENGTH]; uint8_t new_encryption_key[WALLET_ENCRYPTION_KEY_LENGTH]; uint8_t version[4]; uint8_t *address_buffer; uint8_t one_byte; AddressHandle *handles_buffer; AddressHandle ah; PointAffine public_key; PointAffine *public_key_buffer; int abort; int is_zero; int abort_duplicate; int abort_error; int i; int j; initTests(__FILE__); initWalletTest(); memset(encryption_key, 0, WALLET_ENCRYPTION_KEY_LENGTH); setEncryptionKey(encryption_key); // Blank out non-volatile storage area (set to all nulls). temp[0] = 0; for (i = 0; i < TEST_FILE_SIZE; i++) { fwrite(temp, 1, 1, wallet_test_file); } // sanitiseNonVolatileStorage() should nuke everything. if (sanitiseNonVolatileStorage(0, 0xffffffff) == WALLET_NO_ERROR) { reportSuccess(); } else { printf("Cannot nuke NV storage using sanitiseNonVolatileStorage()\n"); reportFailure(); } // Check that the version field is "wallet not there". if (getWalletInfo(version, temp) == WALLET_NO_ERROR) { reportSuccess(); } else { printf("getWalletInfo() failed after sanitiseNonVolatileStorage() was called\n"); reportFailure(); } if (readU32LittleEndian(version) == VERSION_NOTHING_THERE) { reportSuccess(); } else { printf("sanitiseNonVolatileStorage() does not set version to nothing there\n"); reportFailure(); } // initWallet() hasn't been called yet, so nearly every function should // return WALLET_NOT_THERE somehow. checkFunctionsReturnWalletNotThere(); // The non-volatile storage area was blanked out, so there shouldn't be a // (valid) wallet there. if (initWallet() == WALLET_NOT_THERE) { reportSuccess(); } else { printf("initWallet() doesn't recognise when wallet isn't there\n"); reportFailure(); } // Try creating a wallet and testing initWallet() on it. memcpy(name, "123456789012345678901234567890abcdefghij", NAME_LENGTH); if (newWallet(name) == WALLET_NO_ERROR) { reportSuccess(); } else { printf("Could not create new wallet\n"); reportFailure(); } if (initWallet() == WALLET_NO_ERROR) { reportSuccess(); } else { printf("initWallet() does not recognise new wallet\n"); reportFailure(); } if ((getNumAddresses() == 0) && (walletGetLastError() == WALLET_EMPTY)) { reportSuccess(); } else { printf("New wallet isn't empty\n"); reportFailure(); } // Check that the version field is "unencrypted wallet". if (getWalletInfo(version, temp) == WALLET_NO_ERROR) { reportSuccess(); } else { printf("getWalletInfo() failed after newWallet() was called\n"); reportFailure(); } if (readU32LittleEndian(version) == VERSION_UNENCRYPTED) { reportSuccess(); } else { printf("newWallet() does not set version to unencrypted wallet\n"); reportFailure(); } // Check that sanitise_nv_wallet() deletes wallet. if (sanitiseNonVolatileStorage(0, 0xffffffff) == WALLET_NO_ERROR) { reportSuccess(); } else { printf("Cannot nuke NV storage using sanitiseNonVolatileStorage()\n"); reportFailure(); } if (initWallet() == WALLET_NOT_THERE) { reportSuccess(); } else { printf("sanitiseNonVolatileStorage() isn't deleting wallet\n"); reportFailure(); } // Make some new addresses, then create a new wallet and make sure the // new wallet is empty (i.e. check that newWallet() deletes existing // wallet). newWallet(name); if (makeNewAddress(temp, &public_key) != BAD_ADDRESS_HANDLE) { reportSuccess(); } else { printf("Couldn't create new address in new wallet\n"); reportFailure(); } newWallet(name); if ((getNumAddresses() == 0) && (walletGetLastError() == WALLET_EMPTY)) { reportSuccess(); } else { printf("newWallet() doesn't delete existing wallet\n"); reportFailure(); } // Unload wallet and make sure everything realises that the wallet is // not loaded. if (uninitWallet() == WALLET_NO_ERROR) { reportSuccess(); } else { printf("uninitWallet() failed to do its basic job\n"); reportFailure(); } checkFunctionsReturnWalletNotThere(); // Load wallet again. Since there is actually a wallet there, this // should succeed. if (initWallet() == WALLET_NO_ERROR) { reportSuccess(); } else { printf("uninitWallet() appears to be permanent\n"); reportFailure(); } // Change bytes in non-volatile memory and make sure initWallet() fails // because of the checksum check. if (uninitWallet() != WALLET_NO_ERROR) { printf("uninitWallet() failed to do its basic job 2\n"); reportFailure(); } abort = 0; for (i = 0; i < WALLET_RECORD_LENGTH; i++) { if (nonVolatileRead(&one_byte, (uint32_t)i, 1) != NV_NO_ERROR) { printf("NV read fail\n"); abort = 1; break; } one_byte++; if (nonVolatileWrite(&one_byte, (uint32_t)i, 1) != NV_NO_ERROR) { printf("NV write fail\n"); abort = 1; break; } if (initWallet() == WALLET_NO_ERROR) { printf("Wallet still loads when wallet checksum is wrong, offset = %d\n", i); abort = 1; break; } one_byte--; if (nonVolatileWrite(&one_byte, (uint32_t)i, 1) != NV_NO_ERROR) { printf("NV write fail\n"); abort = 1; break; } } if (!abort) { reportSuccess(); } else { reportFailure(); } // Create 2 new wallets and check that their addresses aren't the same newWallet(name); if (makeNewAddress(address1, &public_key) != BAD_ADDRESS_HANDLE) { reportSuccess(); } else { printf("Couldn't create new address in new wallet\n"); reportFailure(); } newWallet(name); memset(address2, 0, 20); memset(&public_key, 0, sizeof(PointAffine)); if (makeNewAddress(address2, &public_key) != BAD_ADDRESS_HANDLE) { reportSuccess(); } else { printf("Couldn't create new address in new wallet\n"); reportFailure(); } if (memcmp(address1, address2, 20)) { reportSuccess(); } else { printf("New wallets are creating identical addresses\n"); reportFailure(); } // Check that makeNewAddress() wrote to its outputs. is_zero = 1; for (i = 0; i < 20; i++) { if (address2[i] != 0) { is_zero = 0; break; } } if (is_zero) { printf("makeNewAddress() doesn't write the address\n"); reportFailure(); } else { reportSuccess(); } if (bigIsZero(public_key.x)) { printf("makeNewAddress() doesn't write the public key\n"); reportFailure(); } else { reportSuccess(); } // Make some new addresses, up to a limit. // Also check that addresses are unique. newWallet(name); abort = 0; address_buffer = malloc(MAX_TESTING_ADDRESSES * 20); for (i = 0; i < MAX_TESTING_ADDRESSES; i++) { if (makeNewAddress(&(address_buffer[i * 20]), &public_key) == BAD_ADDRESS_HANDLE) { printf("Couldn't create new address in new wallet\n"); abort = 1; break; } for (j = 0; j < i; j++) { if (!memcmp(&(address_buffer[i * 20]), &(address_buffer[j * 20]), 20)) { printf("Wallet addresses aren't unique\n"); abort = 1; break; } } if (abort) { break; } } free(address_buffer); if (!abort) { reportSuccess(); } else { reportFailure(); } // The wallet should be full now. // Check that making a new address now causes an appropriate error. if (makeNewAddress(temp, &public_key) == BAD_ADDRESS_HANDLE) { if (walletGetLastError() == WALLET_FULL) { reportSuccess(); } else { printf("Creating a new address on a full wallet gives incorrect error\n"); reportFailure(); } } else { printf("Creating a new address on a full wallet succeeds (it's not supposed to)\n"); reportFailure(); } // Check that getNumAddresses() fails when the wallet is empty. newWallet(name); if (getNumAddresses() == 0) { if (walletGetLastError() == WALLET_EMPTY) { reportSuccess(); } else { printf("getNumAddresses() doesn't recognise wallet is empty\n"); reportFailure(); } } else { printf("getNumAddresses() succeeds when used on empty wallet\n"); reportFailure(); } // Create a bunch of addresses in the (now empty) wallet and check that // getNumAddresses() returns the right number. address_buffer = malloc(MAX_TESTING_ADDRESSES * 20); public_key_buffer = malloc(MAX_TESTING_ADDRESSES * sizeof(PointAffine)); handles_buffer = malloc(MAX_TESTING_ADDRESSES * sizeof(AddressHandle)); abort = 0; for (i = 0; i < MAX_TESTING_ADDRESSES; i++) { ah = makeNewAddress(&(address_buffer[i * 20]), &(public_key_buffer[i])); handles_buffer[i] = ah; if (ah == BAD_ADDRESS_HANDLE) { printf("Couldn't create new address in new wallet\n"); abort = 1; reportFailure(); break; } } if (!abort) { reportSuccess(); } if (getNumAddresses() == MAX_TESTING_ADDRESSES) { reportSuccess(); } else { printf("getNumAddresses() returns wrong number of addresses\n"); reportFailure(); } // The wallet should contain unique addresses. abort_duplicate = 0; for (i = 0; i < MAX_TESTING_ADDRESSES; i++) { for (j = 0; j < i; j++) { if (!memcmp(&(address_buffer[i * 20]), &(address_buffer[j * 20]), 20)) { printf("Wallet has duplicate addresses\n"); abort_duplicate = 1; reportFailure(); break; } } } if (!abort_duplicate) { reportSuccess(); } // The wallet should contain unique public keys. abort_duplicate = 0; for (i = 0; i < MAX_TESTING_ADDRESSES; i++) { for (j = 0; j < i; j++) { if (bigCompare(public_key_buffer[i].x, public_key_buffer[j].x) == BIGCMP_EQUAL) { printf("Wallet has duplicate public keys\n"); abort_duplicate = 1; reportFailure(); break; } } } if (!abort_duplicate) { reportSuccess(); } // The address handles should start at 1 and be sequential. abort = 0; for (i = 0; i < MAX_TESTING_ADDRESSES; i++) { if (handles_buffer[i] != (AddressHandle)(i + 1)) { printf("Address handle %d should be %d, but got %d\n", i, i + 1, (int)handles_buffer[i]); abort = 1; reportFailure(); break; } } if (!abort) { reportSuccess(); } // While there's a bunch of addresses in the wallet, check that // getAddressAndPublicKey() obtains the same address and public key as // makeNewAddress(). abort_error = 0; abort = 0; for (i = 0; i < MAX_TESTING_ADDRESSES; i++) { ah = handles_buffer[i]; if (getAddressAndPublicKey(address1, &public_key, ah) != WALLET_NO_ERROR) { printf("Couldn't obtain address in wallet\n"); abort_error = 1; reportFailure(); break; } if ((memcmp(address1, &(address_buffer[i * 20]), 20)) || (bigCompare(public_key.x, public_key_buffer[i].x) != BIGCMP_EQUAL) || (bigCompare(public_key.y, public_key_buffer[i].y) != BIGCMP_EQUAL)) { printf("getAddressAndPublicKey() returned mismatching address or public key, ah = %d\n", i); abort = 1; reportFailure(); break; } } if (!abort) { reportSuccess(); } if (!abort_error) { reportSuccess(); } // Test getAddressAndPublicKey() and getPrivateKey() functions using // invalid and then valid address handles. if (getAddressAndPublicKey(temp, &public_key, 0) == WALLET_INVALID_HANDLE) { reportSuccess(); } else { printf("getAddressAndPublicKey() doesn't recognise 0 as invalid address handle\n"); reportFailure(); } if (getPrivateKey(temp, 0) == WALLET_INVALID_HANDLE) { reportSuccess(); } else { printf("getPrivateKey() doesn't recognise 0 as invalid address handle\n"); reportFailure(); } if (getAddressAndPublicKey(temp, &public_key, BAD_ADDRESS_HANDLE) == WALLET_INVALID_HANDLE) { reportSuccess(); } else { printf("getAddressAndPublicKey() doesn't recognise BAD_ADDRESS_HANDLE as invalid address handle\n"); reportFailure(); } if (getPrivateKey(temp, BAD_ADDRESS_HANDLE) == WALLET_INVALID_HANDLE) { reportSuccess(); } else { printf("getPrivateKey() doesn't recognise BAD_ADDRESS_HANDLE as invalid address handle\n"); reportFailure(); } if (getAddressAndPublicKey(temp, &public_key, handles_buffer[0]) == WALLET_NO_ERROR) { reportSuccess(); } else { printf("getAddressAndPublicKey() doesn't recognise valid address handle\n"); reportFailure(); } if (getPrivateKey(temp, handles_buffer[0]) == WALLET_NO_ERROR) { reportSuccess(); } else { printf("getPrivateKey() doesn't recognise valid address handle\n"); reportFailure(); } free(address_buffer); free(public_key_buffer); free(handles_buffer); // Check that changeEncryptionKey() works. memset(new_encryption_key, 0, WALLET_ENCRYPTION_KEY_LENGTH); new_encryption_key[0] = 1; if (changeEncryptionKey(new_encryption_key) == WALLET_NO_ERROR) { reportSuccess(); } else { printf("Couldn't change encryption key\n"); reportFailure(); } // Check that the version field is "encrypted wallet". if (getWalletInfo(version, temp) == WALLET_NO_ERROR) { reportSuccess(); } else { printf("getWalletInfo() failed after changeEncryptionKey() was called\n"); reportFailure(); } if (readU32LittleEndian(version) == VERSION_IS_ENCRYPTED) { reportSuccess(); } else { printf("changeEncryptionKey() does not set version to encrypted wallet\n"); reportFailure(); } // Check name matches what was given in newWallet(). if (!memcmp(temp, name, NAME_LENGTH)) { reportSuccess(); } else { printf("getWalletInfo() doesn't return correct name when wallet is loaded\n"); reportFailure(); } // Check that getWalletInfo() still works after unloading wallet. uninitWallet(); if (getWalletInfo(version, temp) == WALLET_NO_ERROR) { reportSuccess(); } else { printf("getWalletInfo() failed after uninitWallet() was called\n"); reportFailure(); } if (readU32LittleEndian(version) == VERSION_IS_ENCRYPTED) { reportSuccess(); } else { printf("uninitWallet() caused wallet version to change\n"); reportFailure(); } // Check name matches what was given in newWallet(). if (!memcmp(temp, name, NAME_LENGTH)) { reportSuccess(); } else { printf("getWalletInfo() doesn't return correct name when wallet is not loaded\n"); reportFailure(); } // Change wallet's name and check that getWalletInfo() reflects the // name change. initWallet(); memcpy(name, "HHHHH HHHHHHHHHHHHHHHHH HHHHHHHHHHHHHH ", NAME_LENGTH); if (changeWalletName(name) == WALLET_NO_ERROR) { reportSuccess(); } else { printf("changeWalletName() couldn't change name\n"); reportFailure(); } getWalletInfo(version, temp); if (!memcmp(temp, name, NAME_LENGTH)) { reportSuccess(); } else { printf("getWalletInfo() doesn't reflect name change\n"); reportFailure(); } // Check that name change is preserved when unloading and loading a // wallet. uninitWallet(); getWalletInfo(version, temp); if (!memcmp(temp, name, NAME_LENGTH)) { reportSuccess(); } else { printf("getWalletInfo() doesn't reflect name change after unloading wallet\n"); reportFailure(); } // Check that initWallet() succeeds (changing the name changes the // checksum, so this tests whether the checksum was updated). if (initWallet() == WALLET_NO_ERROR) { reportSuccess(); } else { printf("initWallet() failed after name change\n"); reportFailure(); } getWalletInfo(version, temp); if (!memcmp(temp, name, NAME_LENGTH)) { reportSuccess(); } else { printf("getWalletInfo() doesn't reflect name change after reloading wallet\n"); reportFailure(); } // Check that loading the wallet with the old key fails. uninitWallet(); setEncryptionKey(encryption_key); if (initWallet() == WALLET_NOT_THERE) { reportSuccess(); } else { printf("Loading wallet with old encryption key succeeds\n"); reportFailure(); } // Check that loading the wallet with the new key succeeds. uninitWallet(); setEncryptionKey(new_encryption_key); if (initWallet() == WALLET_NO_ERROR) { reportSuccess(); } else { printf("Loading wallet with new encryption key fails\n"); reportFailure(); } // Test the getAddressAndPublicKey() and getPrivateKey() functions on an // empty wallet. newWallet(name); if (getAddressAndPublicKey(temp, &public_key, 0) == WALLET_EMPTY) { reportSuccess(); } else { printf("getAddressAndPublicKey() doesn't deal with empty wallets correctly\n"); reportFailure(); } if (getPrivateKey(temp, 0) == WALLET_EMPTY) { reportSuccess(); } else { printf("getPrivateKey() doesn't deal with empty wallets correctly\n"); reportFailure(); } fclose(wallet_test_file); finishTests(); exit(0); }
/** 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) }