Handle Digital Bitbox errors

This commit is contained in:
Andrew Chow 2019-01-31 19:31:43 -05:00
parent d279a7e21c
commit 7a8e38c78c

View File

@ -15,7 +15,7 @@ import sys
import time
from ..hwwclient import HardwareWalletClient
from ..errors import BadArgumentError, NoPasswordError, UnavailableActionError, DeviceFailureError
from ..errors import ActionCanceledError, BadArgumentError, DeviceFailureError, DeviceNotReadyError, NoPasswordError, UnavailableActionError, DeviceFailureError
from ..serializations import CTransaction, PSBT, hash256, hash160, ser_sig_der, ser_sig_compact, ser_compact_size
from ..base58 import get_xpub_fingerprint, decode, to_address, xpub_main_2_test, get_xpub_fingerprint_hex
@ -31,6 +31,90 @@ HWW_CMD = 0x80 + 0x40 + 0x01
DBB_VENDOR_ID = 0x03eb
DBB_DEVICE_ID = 0x2402
# Errors codes from the device
bad_args = [
102, # The password length must be at least " STRINGIFY(PASSWORD_LEN_MIN) " characters.
103, # No input received.
104, # Invalid command.
105, # Only one command allowed at a time.
109, # JSON parse error.
204, # Invalid seed.
253, # Incorrect serialized pubkey length. A 33-byte hexadecimal value (66 characters) is expected.
254, # Incorrect serialized pubkey hash length. A 32-byte hexadecimal value (64 characters) is expected.
256, # Failed to pair with second factor, because the previously received hash of the public key does not match the computed hash of the public key.
300, # Incorrect pubkey length. A 33-byte hexadecimal value (66 characters) is expected.
301, # Incorrect hash length. A 32-byte hexadecimal value (64 characters) is expected.
304, # Incorrect TFA pin.
411, # Filenames limited to alphanumeric values, hyphens, and underscores.
412, # Please provide an encryption key.
112, # Device password matches reset password. Disabling reset password.
]
device_failures = [
101, # Please set a password.
107, # Output buffer overflow.
200, # Seed creation requires an SD card for automatic encrypted backup of the seed.
250, # Master key not present.
251, # Could not generate key.
252, # Could not generate ECDH secret.
303, # Could not sign.
400, # Please insert SD card.
401, # Could not mount the SD card.
402, # Could not open a file to write - it may already exist.
403, # Could not open the directory.
405, # Could not write the file.
407, # Could not read the file.
408, # May not have erased all files (or no file present).
410, # Backup file does not match wallet.
500, # Chip communication error.
501, # Could not read flash.
502, # Could not encrypt.
110, # Too many failed access attempts. Device reset.
111, # Device locked. Erase device to access this command.
113, # Due to many login attempts, the next login requires holding the touch button for 3 seconds.
900, # attempts remain before the device is reset.
901, # Ignored for non-embedded testing.
902, # Too many backup files to read. The list is truncated.
903, # attempts remain before the device is reset. The next login requires holding the touch button.
]
cancels = [
600, # Aborted by user.
601, # Touchbutton timed out.
]
ERR_MEM_SETUP = 503 # Device initialization in progress.
class DBBError(Exception):
def __init__(self, error):
Exception.__init__(self)
self.error = error
def get_error(self):
return self.error['error']['message']
def get_code(self):
return self.error['error']['code']
def __str__(self):
return 'Error: {}, Code: {}'.format(self.error['error']['message'], self.error['error']['code'])
def digitalbitbox_exception(f):
def func(*args, **kwargs):
try:
return f(*args, **kwargs)
except DBBError as e:
if e.get_code() in bad_args:
raise BadArgumentError(e.get_error())
elif e.get_code() in device_failures:
raise DeviceFailureError(e.get_error())
elif e.get_code() in cancels:
raise ActionCanceledError(e.get_error())
elif e.get_code() == ERR_MEM_SETUP:
raise DeviceNotReadyError(e.get_error())
return func
def aes_encrypt_with_iv(key, iv, data):
aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)
aes = pyaes.Encrypter(aes_cbc)
@ -221,12 +305,13 @@ class DigitalbitboxClient(HardwareWalletClient):
# Must return a dict with the xpub
# Retrieves the public key at the specified BIP 32 derivation path
@digitalbitbox_exception
def get_pubkey_at_path(self, path):
if '\'' not in path and 'h' not in path and 'H' not in path:
raise BadArgumentError('The digital bitbox requires one part of the derivation path to be derived using hardened keys')
reply = send_encrypt('{"xpub":"' + path + '"}', self.password, self.device)
if 'error' in reply:
return reply
raise DBBError(reply)
if self.is_testnet:
return {'xpub':xpub_main_2_test(reply['xpub'])}
@ -235,6 +320,7 @@ class DigitalbitboxClient(HardwareWalletClient):
# Must return a hex string with the signed transaction
# The tx must be in the PSBT format
@digitalbitbox_exception
def sign_tx(self, tx):
# Create a transaction with all scriptsigs blanekd out
@ -359,12 +445,12 @@ class DigitalbitboxClient(HardwareWalletClient):
reply = send_encrypt(to_send, self.password, self.device)
logging.debug(reply)
if 'error' in reply:
return reply
raise DBBError(reply)
print("Touch the device for 3 seconds to sign. Touch briefly to cancel", file=sys.stderr)
reply = send_encrypt(to_send, self.password, self.device)
logging.debug(reply)
if 'error' in reply:
return reply
raise DBBError(reply)
# Extract sigs
sigs = []
@ -384,6 +470,7 @@ class DigitalbitboxClient(HardwareWalletClient):
# Must return a base64 encoded string with the signed message
# The message can be any string
@digitalbitbox_exception
def sign_message(self, message, keypath):
to_hash = b""
to_hash += self.message_magic
@ -401,12 +488,12 @@ class DigitalbitboxClient(HardwareWalletClient):
reply = send_encrypt(to_send, self.password, self.device)
logging.debug(reply)
if 'error' in reply:
return reply
raise DBBError(reply)
print("Touch the device for 3 seconds to sign. Touch briefly to cancel", file=sys.stderr)
reply = send_encrypt(to_send, self.password, self.device)
logging.debug(reply)
if 'error' in reply:
return reply
raise DBBError(reply)
sig = binascii.unhexlify(reply['sign'][0]['sig'])
r = sig[0:32]
@ -422,6 +509,7 @@ class DigitalbitboxClient(HardwareWalletClient):
raise UnavailableActionError('The Digital Bitbox does not have a screen to display addresses on')
# Setup a new device
@digitalbitbox_exception
def setup_device(self, label='', passphrase=''):
# Make sure this is not initialized
reply = send_encrypt('{"device" : "info"}', self.password, self.device)
@ -446,6 +534,7 @@ class DigitalbitboxClient(HardwareWalletClient):
return {'success': True}
# Wipe this device
@digitalbitbox_exception
def wipe_device(self):
reply = send_encrypt('{"reset" : "__ERASE__"}', self.password, self.device)
if 'error' in reply:
@ -457,6 +546,7 @@ class DigitalbitboxClient(HardwareWalletClient):
raise UnavailableActionError('The Digital Bitbox does not support restoring via software')
# Begin backup process
@digitalbitbox_exception
def backup_device(self, label='', passphrase=''):
key = stretch_backup_key(passphrase)
backup_filename = format_backup_filename(label)