diff --git a/.cirrus.yml b/.cirrus.yml index 52758e86322e..383c9288f13b 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -153,7 +153,7 @@ task: install_script: - pip install "flake8==7.0.0" "flake8-bugbear==24.1.16" flake8_script: - - flake8 . --count --select="$ELECTRUM_LINTERS" --ignore="$ELECTRUM_LINTERS_IGNORE" --show-source --statistics --exclude "*_pb2.py,electrum/_vendor/" + - flake8 . --count --select="$ELECTRUM_LINTERS" --ignore="$ELECTRUM_LINTERS_IGNORE" --show-source --statistics --exclude "$ELECTRUM_EXCLUDE" env: ELECTRUM_IMAGE: python:3.10 ELECTRUM_REQUIREMENTS: contrib/requirements/requirements.txt @@ -166,6 +166,7 @@ task: # - https://github.com/PyCQA/flake8-bugbear/tree/8c0e7eb04217494d48d0ab093bf5b31db0921989#list-of-warnings ELECTRUM_LINTERS: E9,E101,E129,E273,E274,E703,E71,E722,F63,F7,F82,W191,W29,B ELECTRUM_LINTERS_IGNORE: B007,B009,B010,B019,B036 + ELECTRUM_EXCLUDE: "*_pb2.py,electrum/_vendor/,electrum/plugins/keepkey/keepkeylib/" - name: "linter: Flake8 Non-Mandatory" env: ELECTRUM_LINTERS: E,F,W,C90,B diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec index af5e65b581f2..86ccd31f3bd9 100644 --- a/contrib/build-wine/deterministic.spec +++ b/contrib/build-wine/deterministic.spec @@ -41,7 +41,6 @@ datas += collect_data_files(f"{PYPKG}.plugins") datas += collect_data_files('trezorlib') # TODO is this needed? and same question for other hww libs datas += collect_data_files('safetlib') datas += collect_data_files('btchip') -datas += collect_data_files('keepkeylib') datas += collect_data_files('ckcc') datas += collect_data_files('bitbox02') diff --git a/contrib/deterministic-build/requirements-hw.txt b/contrib/deterministic-build/requirements-hw.txt index e0d8cd0467c6..0dbef4fff32f 100644 --- a/contrib/deterministic-build/requirements-hw.txt +++ b/contrib/deterministic-build/requirements-hw.txt @@ -279,10 +279,6 @@ hidapi==0.14.0 \ idna==3.6 \ --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f -keepkey==6.3.1 \ - --hash=sha256:88e2b5291c85c8e8567732f675697b88241082884aa1aba32257f35ee722fc09 \ - --hash=sha256:cef1e862e195ece3e42640a0f57d15a63086fd1dedc8b5ddfcbc9c2657f0bb1e \ - --hash=sha256:f369d640c65fec7fd8e72546304cdc768c04224a6b9b00a19dc2cd06fa9d2a6b ledger-bitcoin==0.3.0 \ --hash=sha256:ad9cdeaf33a45562bbd5bae6751025b869a2f81d6eb0267dd062a01f5925a4d5 \ --hash=sha256:e7c33404d02044c3810b294a510f7ad97bc65ab12dbdd180d873f2b4ebc0711a diff --git a/contrib/osx/osx.spec b/contrib/osx/osx.spec index 3ab38bb34859..6e08ab9d2547 100644 --- a/contrib/osx/osx.spec +++ b/contrib/osx/osx.spec @@ -44,7 +44,6 @@ datas += collect_data_files(f"{PYPKG}.plugins") datas += collect_data_files('trezorlib') # TODO is this needed? and same question for other hww libs datas += collect_data_files('safetlib') datas += collect_data_files('btchip') -datas += collect_data_files('keepkeylib') datas += collect_data_files('ckcc') datas += collect_data_files('bitbox02') diff --git a/contrib/requirements/requirements-hw.txt b/contrib/requirements/requirements-hw.txt index ee15b8c33cb3..e552142a0f90 100644 --- a/contrib/requirements/requirements-hw.txt +++ b/contrib/requirements/requirements-hw.txt @@ -7,7 +7,11 @@ trezor[hidapi]>=0.13.0,<0.14 safet>=0.1.5 # device plugin: keepkey -keepkey>=6.3.1 +ecdsa>=0.9 +protobuf>=3.20 +mnemonic>=0.8 +hidapi>=0.7.99.post15 +libusb1>=1.6 # device plugin: ledger # note: btchip-python only needed for "legacy" protocol and HW.1 support @@ -25,10 +29,6 @@ bitbox02>=6.2.0 cbor2>=5.4.6,<6.0.0 pyserial>=3.5.0,<4.0.0 -# prefer older protobuf (see #7922) -# (pulled in via e.g. keepkey and bitbox02) -protobuf>=3.20,<4 - # prefer older colorama to avoid needing hatchling # (pulled in via trezor -> click -> colorama) # (pulled in via safet -> click -> colorama) diff --git a/contrib/requirements/requirements.txt b/contrib/requirements/requirements.txt index 4f00c0b74197..e6d6195fc2c1 100644 --- a/contrib/requirements/requirements.txt +++ b/contrib/requirements/requirements.txt @@ -1,5 +1,5 @@ qrcode -protobuf>=3.20,<4 +protobuf>=3.20 qdarkstyle>=3.2 aiorpcx>=0.22.0,<0.25 aiohttp>=3.3.0,<4.0.0 diff --git a/electrum/plugins/keepkey/client.py b/electrum/plugins/keepkey/client.py index 73bf66dc827c..191931fd0220 100644 --- a/electrum/plugins/keepkey/client.py +++ b/electrum/plugins/keepkey/client.py @@ -1,4 +1,4 @@ -from keepkeylib.client import proto, BaseClient, ProtocolMixin +from .keepkeylib.client import proto, BaseClient, ProtocolMixin from .clientbase import KeepKeyClientBase class KeepKeyClient(KeepKeyClientBase, ProtocolMixin, BaseClient): diff --git a/electrum/plugins/keepkey/device-protocol/build_pb.sh b/electrum/plugins/keepkey/device-protocol/build_pb.sh new file mode 100755 index 000000000000..d715d1515bae --- /dev/null +++ b/electrum/plugins/keepkey/device-protocol/build_pb.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +PLUGIN_KEEPKEY="$(dirname "$(readlink -e "$0")")/.." +cd "$PLUGIN_KEEPKEY/device-protocol" + +echo "Building with protoc version: $(protoc --version)" +for i in messages types exchange ; do + protoc --python_out="$PLUGIN_KEEPKEY/keepkeylib/" -I/usr/include -I. $i.proto + i=${i/-/_} + sed -i -Ee 's/^import ([^.]+_pb2)/from . import \1/' "$PLUGIN_KEEPKEY"/keepkeylib/"$i"_pb2.py +done + +sed -i 's/5000\([2-5]\)/6000\1/g' "$PLUGIN_KEEPKEY"/keepkeylib/types_pb2.py diff --git a/electrum/plugins/keepkey/device-protocol/exchange.proto b/electrum/plugins/keepkey/device-protocol/exchange.proto new file mode 100644 index 000000000000..5e7c47178fef --- /dev/null +++ b/electrum/plugins/keepkey/device-protocol/exchange.proto @@ -0,0 +1,47 @@ +/* + * Exchange communication + * + */ + +syntax = "proto2"; + +// Sugar for easier handling in Java +option java_package = "com.keepkey.device-protocol"; +option java_outer_classname = "KeepKeyExchange"; + +/** + * Structure representing address for various coin types (BTC, LTC, and etc). + * @used in ExchangeResponse + */ +message ExchangeAddress { + optional string coin_type = 1; + optional string address = 2; + optional string dest_tag = 3; + // formerly rs_address = 4; +} + +enum OrderType { + Precise = 0; + Quick = 1; +} + +message ExchangeResponseV2 { + optional ExchangeAddress deposit_address = 1; + optional bytes deposit_amount = 2; + optional int64 expiration = 3; + optional bytes quoted_rate = 4; + optional ExchangeAddress withdrawal_address = 5; + optional bytes withdrawal_amount = 6; + optional ExchangeAddress return_address = 7; + optional bytes api_key = 8; + optional bytes miner_fee = 9; + optional bytes order_id = 10; + optional OrderType type = 11 [default=Precise]; +} + +message SignedExchangeResponse { + // Formerly response = 1; + optional bytes signature = 2; + optional ExchangeResponseV2 responseV2 = 3; +} + diff --git a/electrum/plugins/keepkey/device-protocol/messages.proto b/electrum/plugins/keepkey/device-protocol/messages.proto new file mode 100644 index 000000000000..1111c56dc7ed --- /dev/null +++ b/electrum/plugins/keepkey/device-protocol/messages.proto @@ -0,0 +1,795 @@ +/* + * Messages for KeepKey Communication + * + */ + +syntax = "proto2"; + +// Sugar for easier handling in Java +option java_package = "com.keepkey.deviceprotocol"; +option java_outer_classname = "KeepKeyMessage"; + +import "types.proto"; + +/** + * Mapping between KeepKey wire identifier (uint) and a protobuf message + */ +enum MessageType { + MessageType_Initialize = 0 [(wire_in) = true]; + MessageType_Ping = 1 [(wire_in) = true]; + MessageType_Success = 2 [(wire_out) = true]; + MessageType_Failure = 3 [(wire_out) = true]; + MessageType_ChangePin = 4 [(wire_in) = true]; + MessageType_WipeDevice = 5 [(wire_in) = true]; + MessageType_FirmwareErase = 6 [(wire_in) = true]; + MessageType_FirmwareUpload = 7 [(wire_in) = true]; + MessageType_GetEntropy = 9 [(wire_in) = true]; + MessageType_Entropy = 10 [(wire_out) = true]; + MessageType_GetPublicKey = 11 [(wire_in) = true]; + MessageType_PublicKey = 12 [(wire_out) = true]; + MessageType_LoadDevice = 13 [(wire_in) = true]; + MessageType_ResetDevice = 14 [(wire_in) = true]; + MessageType_SignTx = 15 [(wire_in) = true]; + // Formerly SimpleSignTx = 16 + MessageType_Features = 17 [(wire_out) = true]; + MessageType_PinMatrixRequest = 18 [(wire_out) = true]; + MessageType_PinMatrixAck = 19 [(wire_in) = true]; + MessageType_Cancel = 20 [(wire_in) = true]; + MessageType_TxRequest = 21 [(wire_out) = true]; + MessageType_TxAck = 22 [(wire_in) = true]; + MessageType_CipherKeyValue = 23 [(wire_in) = true]; + MessageType_ClearSession = 24 [(wire_in) = true]; + MessageType_ApplySettings = 25 [(wire_in) = true]; + MessageType_ButtonRequest = 26 [(wire_out) = true]; + MessageType_ButtonAck = 27 [(wire_in) = true]; + MessageType_GetAddress = 29 [(wire_in) = true]; + MessageType_Address = 30 [(wire_out) = true]; + MessageType_EntropyRequest = 35 [(wire_out) = true]; + MessageType_EntropyAck = 36 [(wire_in) = true]; + MessageType_SignMessage = 38 [(wire_in) = true]; + MessageType_VerifyMessage = 39 [(wire_in) = true]; + MessageType_MessageSignature = 40 [(wire_out) = true]; + MessageType_PassphraseRequest = 41 [(wire_out) = true]; + MessageType_PassphraseAck = 42 [(wire_in) = true]; + // Formerly EstimateTxSize = 43 + // Formerly TxSize = 44 + MessageType_RecoveryDevice = 45 [(wire_in) = true]; + MessageType_WordRequest = 46 [(wire_out) = true]; + MessageType_WordAck = 47 [(wire_in) = true]; + MessageType_CipheredKeyValue = 48 [(wire_out) = true]; + MessageType_EncryptMessage = 49 [(wire_in) = true]; + MessageType_EncryptedMessage = 50 [(wire_out) = true]; + MessageType_DecryptMessage = 51 [(wire_in) = true]; + MessageType_DecryptedMessage = 52 [(wire_out) = true]; + MessageType_SignIdentity = 53 [(wire_in) = true]; + MessageType_SignedIdentity = 54 [(wire_out) = true]; + MessageType_GetFeatures = 55 [(wire_in) = true]; + MessageType_CharacterRequest = 80 [(wire_out) = true]; + MessageType_CharacterAck = 81 [(wire_in) = true]; + MessageType_RawTxAck = 82 [(wire_in) = true]; + MessageType_ApplyPolicies = 83 [(wire_in) = true]; + MessageType_FlashHash = 84 [(wire_in) = true]; + MessageType_FlashWrite = 85 [(wire_in) = true]; + MessageType_FlashHashResponse = 86 [(wire_out) = true]; + MessageType_DebugLinkFlashDump = 87 [(wire_debug_in) = true]; + MessageType_DebugLinkFlashDumpResponse = 88 [(wire_debug_out) = true]; + MessageType_SoftReset = 89 [(wire_debug_in) = true]; + MessageType_DebugLinkDecision = 100 [(wire_debug_in) = true]; + MessageType_DebugLinkGetState = 101 [(wire_debug_in) = true]; + MessageType_DebugLinkState = 102 [(wire_debug_out) = true]; + MessageType_DebugLinkStop = 103 [(wire_debug_in) = true]; + MessageType_DebugLinkLog = 104 [(wire_debug_out) = true]; + MessageType_DebugLinkFillConfig = 105 [(wire_debug_out) = true]; + MessageType_GetCoinTable = 106 [(wire_in) = true]; + MessageType_CoinTable = 107 [(wire_out) = true]; +} + +//////////////////// +// Basic messages // +//////////////////// + +/** + * Request: Reset device to default state and ask for device details + * @next Features + */ +message Initialize { +} + +/** + * Request: Ask for device details (no device reset) + * @next Features + */ +message GetFeatures { +} + +/** + * Response: Reports various information about the device + * @prev Initialize + * @prev GetFeatures + */ +message Features { + optional string vendor = 1; // name of the manufacturer, e.g. "bitcointrezor.com" + optional uint32 major_version = 2; // major version of the device, e.g. 1 + optional uint32 minor_version = 3; // minor version of the device, e.g. 0 + optional uint32 patch_version = 4; // patch version of the device, e.g. 0 + optional bool bootloader_mode = 5; // is device in bootloader mode? + optional string device_id = 6; // device's unique identifier + optional bool pin_protection = 7; // is device protected by PIN? + optional bool passphrase_protection = 8; // is node/mnemonic encrypted using passphrase? + optional string language = 9; // device language + optional string label = 10; // device description label + repeated CoinType coins = 11; // supported coins (Deprecated. Use GetCoinTable instead) + optional bool initialized = 12; // does device contain seed? + optional bytes revision = 13; // SCM revision of firmware + optional bytes bootloader_hash = 14; // double sha256 hash of the bootloader + optional bool imported = 15; // was storage imported from an external source? + optional bool pin_cached = 16; // is PIN already cached in session? + optional bool passphrase_cached = 17; // is passphrase already cached in session? + repeated PolicyType policies = 18; // policies + optional string model = 21; // device hardware model + optional string firmware_variant = 22; // Firmware variant + optional bytes firmware_hash = 23; // double sha256 hash of the firmware + optional bool no_backup = 24; // Device was initialized without displaying recovery sentence. +} + +/** + * Request: Ask the device for its list of supported coins + * @next CoinTable + */ +message GetCoinTable { + optional uint32 start = 1; + optional uint32 end = 2; +} + +/** + * Response: A subset of the supported coins + * @prev GetCoinTable + */ +message CoinTable { + repeated CoinType table = 1; + optional uint32 num_coins = 2; + optional uint32 chunk_size = 3; +} + +/** + * Request: clear session (removes cached PIN, passphrase, etc). + * @next Success + */ +message ClearSession { +} + +/** + * Request: change language and/or label of the device + * @next Success + * @next Failure + * @next ButtonRequest + * @next PinMatrixRequest + */ +message ApplySettings { + optional string language = 1; + optional string label = 2; + optional bool use_passphrase = 3; + optional uint32 auto_lock_delay_ms = 4; + optional uint32 u2f_counter = 5; +} + +/** + * Request: Starts workflow for setting/changing/removing the PIN + * @next ButtonRequest + * @next PinMatrixRequest + */ +message ChangePin { + optional bool remove = 1; // is PIN removal requested? +} + +/** + * Request: Test if the device is alive, device sends back the message in Success response + * @next Success + */ +message Ping { + optional string message = 1; // message to send back in Success message + optional bool button_protection = 2; // ask for button press + optional bool pin_protection = 3; // ask for PIN if set in device + optional bool passphrase_protection = 4; // ask for passphrase if set in device +} + +/** + * Response: Success of the previous request + */ +message Success { + optional string message = 1; // human readable description of action or request-specific payload +} + +/** + * Response: Failure of the previous request + */ +message Failure { + optional FailureType code = 1; // computer-readable definition of the error state + optional string message = 2; // human-readable message of the error state +} + +/** + * Response: Device is waiting for HW button press. + * @next ButtonAck + * @next Cancel + */ +message ButtonRequest { + optional ButtonRequestType code = 1; + optional string data = 2; +} + +/** + * Request: Computer agrees to wait for HW button press + * @prev ButtonRequest + */ +message ButtonAck { +} + +/** + * Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme + * @next PinMatrixAck + * @next Cancel + */ +message PinMatrixRequest { + optional PinMatrixRequestType type = 1; +} + +/** + * Request: Computer responds with encoded PIN + * @prev PinMatrixRequest + */ +message PinMatrixAck { + required string pin = 1; // matrix encoded PIN entered by user +} + +/** + * Request: Abort last operation that required user interaction + * @prev ButtonRequest + * @prev PinMatrixRequest + * @prev PassphraseRequest + */ +message Cancel { +} + +/** + * Response: Device awaits encryption passphrase + * @next PassphraseAck + * @next Cancel + */ +message PassphraseRequest { +} + +/** + * Request: Send passphrase back + * @prev PassphraseRequest + */ +message PassphraseAck { + required string passphrase = 1; +} + +/** + * Request: Request a sample of random data generated by hardware RNG. May be used for testing. + * @next ButtonRequest + * @next Entropy + * @next Failure + */ +message GetEntropy { + required uint32 size = 1; // size of requested entropy +} + +/** + * Response: Reply with random data generated by internal RNG + * @prev GetEntropy + */ +message Entropy { + required bytes entropy = 1; // stream of random generated bytes +} + +/** + * Request: Ask device for public key corresponding to address_n path + * @next PassphraseRequest + * @next PublicKey + * @next Failure + */ +message GetPublicKey { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional string ecdsa_curve_name = 2; // ECDSA curve name to use + optional bool show_display = 3; // optionally show on display before sending the result + optional string coin_name = 4 [default='Bitcoin']; + optional InputScriptType script_type = 5 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.) +} + +/** + * Response: Contains public key derived from device private seed + * @prev GetPublicKey + */ +message PublicKey { + required HDNodeType node = 1; // BIP32 public node + optional string xpub = 2; // serialized form of public node +} + +/** + * Request: Ask device for address corresponding to address_n path + * @next PassphraseRequest + * @next Address + * @next Failure + */ +message GetAddress { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional string coin_name = 2 [default='Bitcoin']; + optional bool show_display = 3 ; // optionally show on display before sending the result + optional MultisigRedeemScriptType multisig = 4; // filled if we are showing a multisig address + optional InputScriptType script_type = 5 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.) +} + +/** + * Response: Contains address derived from device private seed + * @prev GetAddress + */ +message Address { + required string address = 1; // Coin address in Base58 encoding +} + +/** + * Request: Request device to wipe all sensitive data and settings + * @next ButtonRequest + */ +message WipeDevice { +} + +/** + * Request: Load seed and related internal settings from the computer + * @next ButtonRequest + * @next Success + * @next Failure + */ +message LoadDevice { + optional string mnemonic = 1; // seed encoded as BIP-39 mnemonic (12, 18 or 24 words) + optional HDNodeType node = 2; // BIP-32 node + optional string pin = 3; // set PIN protection + optional bool passphrase_protection = 4; // enable master node encryption using passphrase + optional string language = 5 [default='english']; // device language + optional string label = 6; // device label + optional bool skip_checksum = 7; // do not test mnemonic for valid BIP-39 checksum + optional uint32 u2f_counter = 8; // U2F Counter +} + +/** + * Request: Ask device to do initialization involving user interaction + * @next EntropyRequest + * @next Failure + */ +message ResetDevice { + optional bool display_random = 1; // display entropy generated by the device before asking for additional entropy + optional uint32 strength = 2 [default=256]; // strength of seed in bits + optional bool passphrase_protection = 3; // enable master node encryption using passphrase + optional bool pin_protection = 4; // enable PIN protection + optional string language = 5 [default='english']; // device language + optional string label = 6; // device label + optional bool no_backup = 7; // Initialize without ever showing the recovery sentence. + optional uint32 auto_lock_delay_ms = 8; // Screensaver Timeout + optional uint32 u2f_counter = 9; // U2F Counter +} + +/** + * Response: Ask for additional entropy from host computer + * @prev ResetDevice + * @next EntropyAck + */ +message EntropyRequest { +} + +/** + * Request: Provide additional entropy for seed generation function + * @prev EntropyRequest + * @next ButtonRequest + */ +message EntropyAck { + optional bytes entropy = 1; // 256 bits (32 bytes) of random data +} + +/** + * Request: Start recovery workflow asking user for specific words of mnemonic + * Used to recovery device safely even on untrusted computer. + * @next WordRequest + */ +message RecoveryDevice { + optional uint32 word_count = 1; // number of words in BIP-39 mnemonic + optional bool passphrase_protection = 2; // enable master node encryption using passphrase + optional bool pin_protection = 3; // enable PIN protection + optional string language = 4 [default='english']; // device language + optional string label = 5; // device label + optional bool enforce_wordlist = 6; // enforce BIP-39 wordlist during the process + optional bool use_character_cipher = 7; // an optional way to input recovery sentence by character using a cipher + optional uint32 auto_lock_delay_ms = 8; // Screensaver Timeout + optional uint32 u2f_counter = 9; // U2F Counter + optional bool dry_run = 10; // perform dry-run recovery workflow (for safe mnemonic validation) +} + +/** + * Response: Device is waiting for user to enter word of the mnemonic + * Its position is shown only on device's internal display. + * @prev RecoveryDevice + * @prev WordAck + */ +message WordRequest { +} + +/** + * Request: Computer replies with word from the mnemonic + * @prev WordRequest + * @next WordRequest + * @next Success + * @next Failure + */ +message WordAck { + required string word = 1; // one word of mnemonic on asked position +} + +/** + * Response: Device is waiting for user to enter character of the mnemonic using cipher. + * The cipher is shown on device's internal display. + * @prev RecoveryDevice + * @prev CharacterAck + */ +message CharacterRequest { + required uint32 word_pos = 1; // word position in BIP-39 mnemonic + required uint32 character_pos = 2; // character position +} + +/** + * Request: Computer replies with character from the mnemonic using cipher + * @prev CharacterRequest + * @next CharacterRequest + * @next Failure + */ +message CharacterAck { + optional string character = 1; // one character of mnemonic using cipher + optional bool delete = 2; // request to delete previous character from ciphered mnemonic + optional bool done = 3; // marks there are no more characters left for ciphered mnemonic +} + +////////////////////////////// +// Message signing messages // +////////////////////////////// + +/** + * Request: Ask device to sign message + * @next MessageSignature + * @next Failure + */ +message SignMessage { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + required bytes message = 2; // message to be signed + optional string coin_name = 3 [default='Bitcoin']; // coin to use for signing + optional InputScriptType script_type = 4 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.) +} + +/** + * Request: Ask device to verify message + * @next Success + * @next Failure + */ +message VerifyMessage { + optional string address = 1; // address to verify + optional bytes signature = 2; // signature to verify + optional bytes message = 3; // message to verify + optional string coin_name = 4 [default='Bitcoin']; // coin to use for verifying +} + +/** + * Response: Signed message + * @prev SignMessage + */ +message MessageSignature { + optional string address = 1; // address used to sign the message + optional bytes signature = 2; // signature of the message +} + +/////////////////////////// +// Encryption/decryption // +/////////////////////////// + +/** + * Request: Ask device to encrypt message + * @next EncryptedMessage + * @next Failure + */ +message EncryptMessage { + optional bytes pubkey = 1; // public key + optional bytes message = 2; // message to encrypt + optional bool display_only = 3; // show just on display? (don't send back via wire) + repeated uint32 address_n = 4; // BIP-32 path to derive the signing key from master node + optional string coin_name = 5 [default='Bitcoin']; // coin to use for signing +} + +/** + * Response: Encrypted message + * @prev EncryptMessage + */ +message EncryptedMessage { + optional bytes nonce = 1; // nonce used during encryption + optional bytes message = 2; // encrypted message + optional bytes hmac = 3; // message hmac +} + +/** + * Request: Ask device to decrypt message + * @next Success + * @next Failure + */ +message DecryptMessage { + repeated uint32 address_n = 1; // BIP-32 path to derive the decryption key from master node + optional bytes nonce = 2; // nonce used during encryption + optional bytes message = 3; // message to decrypt + optional bytes hmac = 4; // message hmac +} + +/** + * Response: Decrypted message + * @prev DecryptedMessage + */ +message DecryptedMessage { + optional bytes message = 1; // decrypted message + optional string address = 2; // address used to sign the message (if used) +} + +/** + * Request: Ask device to encrypt or decrypt value of given key + * @next CipheredKeyValue + * @next Failure + */ +message CipherKeyValue { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional string key = 2; // key component of key:value + optional bytes value = 3; // value component of key:value + optional bool encrypt = 4; // are we encrypting (True) or decrypting (False)? + optional bool ask_on_encrypt = 5; // should we ask on encrypt operation? + optional bool ask_on_decrypt = 6; // should we ask on decrypt operation? + optional bytes iv = 7; // initialization vector (will be computed if not set) +} + +/** + * Response: Return ciphered/deciphered value + * @prev CipherKeyValue + */ +message CipheredKeyValue { + optional bytes value = 1; // ciphered/deciphered value +} + +////////////////////////////////// +// Transaction signing messages // +////////////////////////////////// + +/** + * Request: Ask device to sign transaction + * @next PassphraseRequest + * @next PinMatrixRequest + * @next TxRequest + * @next Failure + */ +message SignTx { + required uint32 outputs_count = 1; // number of transaction outputs + required uint32 inputs_count = 2; // number of transaction inputs + optional string coin_name = 3 [default='Bitcoin']; // coin to use + optional uint32 version = 4 [default=1]; // transaction version + optional uint32 lock_time = 5 [default=0]; // transaction lock_time + optional uint32 expiry = 6; // only for Decred and Zcash + optional bool overwintered = 7; // only for Zcash + optional uint32 version_group_id = 8; // only for Zcash, nVersionGroupId when overwintered is set + optional uint32 branch_id = 10; // only for Zcash, BRANCH_ID when overwintered is set +} + +/** + * Response: Device asks for information for signing transaction or returns the last result + * If request_index is set, device awaits TxAck message (with fields filled in according to request_type) + * If signature_index is set, 'signature' contains signed input of signature_index's input + * @prev SignTx + * @prev TxAck + */ +message TxRequest { + optional RequestType request_type = 1; // what should be filled in TxAck message? + optional TxRequestDetailsType details = 2; // request for tx details + optional TxRequestSerializedType serialized = 3; // serialized data and request for next +} + +/** + * Request: Reported transaction data + * @prev TxRequest + * @next TxRequest + */ +message TxAck { + optional TransactionType tx = 1; +} + +/** + * Request: Reported raw transaction data + * @prev TxRequest + * @next TxRequest + */ +message RawTxAck { + optional RawTransactionType tx = 1; +} + +/////////////////////// +// Identity messages // +/////////////////////// + +/** + * Request: Ask device to sign identity + * @next SignedIdentity + * @next Failure + */ +message SignIdentity { + optional IdentityType identity = 1; // identity + optional bytes challenge_hidden = 2; // non-visible challenge + optional string challenge_visual = 3; // challenge shown on display (e.g. date+time) + optional string ecdsa_curve_name = 4; // ECDSA curve name to use +} + +/** + * Response: Device provides signed identity + * @prev SignIdentity + */ +message SignedIdentity { + optional string address = 1; // identity address + optional bytes public_key = 2; // identity public key + optional bytes signature = 3; // signature of the identity data +} + +///////////////////// +// Policy messages // +///////////////////// + +/** + * Request: Ask device to apply policy + * @next Success + * @next Failure + * @next ButtonRequest + * @next PinMatrixRequest + */ +message ApplyPolicies { + repeated PolicyType policy = 1; // policy +} + +///////////////////////// +// Bootloader Verification +///////////////////////// + +/** + * Request: Ask the device to return a hash of flash memory + * @next FlashHashResponse + * @next Failure + */ +message FlashHash { + optional uint32 address = 1; + optional uint32 length = 2; + optional bytes challenge = 3; +} + +/** + * Request: Write a chunk of data into flash memory + * @next FlashHashResponse + * @next Failure + */ +message FlashWrite { + optional uint32 address = 1; + optional bytes data = 2; + optional bool erase = 3; +} + +/** + * Response: Returns hash of requested data sector + * @prev FlashHash + * @prev FlashWrite + */ + message FlashHashResponse { + optional bytes data = 1; + } + +/** + * Request: Returns a portion of flash requested + * @next DebugLinkFlashDumpResponse + * @next Failure + */ +message DebugLinkFlashDump { + optional uint32 address = 1; + optional uint32 length = 2; +} + +/** + * Response: flash data + * @prev DebugLinkFlashDump + */ +message DebugLinkFlashDumpResponse { + optional bytes data = 1; +} + +/** + * Request: Ask the device to perform a soft reset + */ +message SoftReset { +} + +///////////////////////// +// Bootloader messages // +///////////////////////// + +/** + * Request: Ask device to erase its firmware + * @next Success + * @next Failure + */ +message FirmwareErase { +} + +/** + * Request: Send firmware in binary form to the device + * @next Success + * @next Failure + */ +message FirmwareUpload { + required bytes payload_hash = 1; // sha256 hash of payload (meta + firmware) + required bytes payload = 2; // firmware to be loaded into device +} + +///////////////////////////////////////////////////////////// +// Debug messages (only available if DebugLink is enabled) // +///////////////////////////////////////////////////////////// + +/** + * Request: "Press" the button on the device + * @next Success + */ +message DebugLinkDecision { + required bool yes_no = 1; // true for "Confirm", false for "Cancel" +} + +/** + * Request: Computer asks for device state + * @next DebugLinkState + */ +message DebugLinkGetState { +} + +/** + * Response: Device current state + * @prev DebugLinkGetState + */ +message DebugLinkState { + optional bytes layout = 1; // raw buffer of display + optional string pin = 2; // current PIN, blank if PIN is not set/enabled + optional string matrix = 3; // current PIN matrix + optional string mnemonic = 4; // current BIP-39 mnemonic + optional HDNodeType node = 5; // current BIP-32 node + optional bool passphrase_protection = 6; // is node/mnemonic encrypted using passphrase? + optional string reset_word = 7; // word on device display during ResetDevice workflow + optional bytes reset_entropy = 8; // current entropy during ResetDevice workflow + optional string recovery_fake_word = 9; // (fake) word on display during RecoveryDevice workflow + optional uint32 recovery_word_pos = 10; // index of mnemonic word the device is expecting during RecoveryDevice workflow + optional string recovery_cipher = 11; // current recovery cipher + optional string recovery_auto_completed_word = 12; // last auto completed recovery word + optional bytes firmware_hash = 13; // hash of the application and meta header + optional bytes storage_hash = 14; // hash of storage +} + +/** + * Request: Ask device to restart + */ +message DebugLinkStop { +} + +/** + * Response: Device wants host to log event + */ +message DebugLinkLog { + optional uint32 level = 1; + optional string bucket = 2; + optional string text = 3; +} + +/** + * Request: Ask device to fill config area with sample data (used for testing firmware upload) + */ +message DebugLinkFillConfig { +} diff --git a/electrum/plugins/keepkey/device-protocol/types.proto b/electrum/plugins/keepkey/device-protocol/types.proto new file mode 100644 index 000000000000..29dfd75fd663 --- /dev/null +++ b/electrum/plugins/keepkey/device-protocol/types.proto @@ -0,0 +1,334 @@ +/* + * Types for KeepKey Communication + * + */ + +syntax = "proto2"; + +// Sugar for easier handling in Java +option java_package = "com.keepkey.deviceprotocol"; +option java_outer_classname = "KeepKeyType"; + +import "google/protobuf/descriptor.proto"; +import "exchange.proto"; + +/** + * Options for specifying message direction and type of wire (normal/debug) + */ +extend google.protobuf.EnumValueOptions { + optional bool wire_in = 50002; // message can be transmitted via wire from PC to TREZOR + optional bool wire_out = 50003; // message can be transmitted via wire from TREZOR to PC + optional bool wire_debug_in = 50004; // message can be transmitted via debug wire from PC to TREZOR + optional bool wire_debug_out = 50005; // message can be transmitted via debug wire from TREZOR to PC +} + +/** + * Type of failures returned by Failure message + * @used_in Failure + */ +enum FailureType { + Failure_UnexpectedMessage = 1; + Failure_ButtonExpected = 2; + Failure_SyntaxError = 3; + Failure_ActionCancelled = 4; + Failure_PinExpected = 5; + Failure_PinCancelled = 6; + Failure_PinInvalid = 7; + Failure_InvalidSignature = 8; + Failure_Other = 9; + Failure_NotEnoughFunds = 10; + Failure_NotInitialized = 11; + Failure_PinMismatch = 12; + Failure_FirmwareError = 99; +} + +/** + * Type of script which will be used for transaction output + * @used_in TxOutputType + */ +enum OutputScriptType { + PAYTOADDRESS = 0; // used for all addresses (bitcoin, p2sh, witness) + PAYTOSCRIPTHASH = 1; // p2sh address (deprecated; use PAYTOADDRESS) + PAYTOMULTISIG = 2; // only for change output + PAYTOOPRETURN = 3; // op_return + PAYTOWITNESS = 4; // only for change output + PAYTOP2SHWITNESS = 5; // only for change output +} + +/** + * Type of script which will be used for transaction output + * @used_in TxInputType + */ +enum InputScriptType { + SPENDADDRESS = 0; // standard p2pkh address + SPENDMULTISIG = 1; // p2sh multisig address + EXTERNAL = 2; // reserved for external inputs (coinjoin) + SPENDWITNESS = 3; // native segwit + SPENDP2SHWITNESS = 4; // segwit over p2sh (backward compatible) +} + +/** + * Type of information required by transaction signing process + * @used_in TxRequest + */ +enum RequestType { + TXINPUT = 0; + TXOUTPUT = 1; + TXMETA = 2; + TXFINISHED = 3; + TXEXTRADATA = 4; +} + +/** + * Type of ouput address specify in transaction + * @used_in TxOutputType + */ +enum OutputAddressType { + SPEND = 0; + TRANSFER = 1; + CHANGE = 2; + EXCHANGE = 3; +} + +/** + * Type of button request + * @used_in ButtonRequest + */ +enum ButtonRequestType { + ButtonRequest_Other = 1; + ButtonRequest_FeeOverThreshold = 2; + ButtonRequest_ConfirmOutput = 3; + ButtonRequest_ResetDevice = 4; + ButtonRequest_ConfirmWord = 5; + ButtonRequest_WipeDevice = 6; + ButtonRequest_ProtectCall = 7; + ButtonRequest_SignTx = 8; + ButtonRequest_FirmwareCheck = 9; + ButtonRequest_Address = 10; + ButtonRequest_FirmwareErase = 11; + ButtonRequest_ConfirmTransferToAccount = 12; + ButtonRequest_ConfirmTransferToNodePath = 13; /* Deprecated!*/ + ButtonRequest_ChangeLabel = 14; + ButtonRequest_ChangeLanguage = 15; + ButtonRequest_EnablePassphrase = 16; + ButtonRequest_DisablePassphrase = 17; + ButtonRequest_EncryptAndSignMessage = 18; + ButtonRequest_EncryptMessage = 19; + ButtonRequest_ImportPrivateKey = 20; + ButtonRequest_ImportRecoverySentence = 21; + ButtonRequest_SignIdentity = 22; + ButtonRequest_Ping = 23; + ButtonRequest_RemovePin = 24; + ButtonRequest_ChangePin = 25; + ButtonRequest_CreatePin = 26; + ButtonRequest_GetEntropy = 27; + ButtonRequest_SignMessage = 28; + ButtonRequest_ApplyPolicies = 29; + ButtonRequest_SignExchange = 30; + ButtonRequest_AutoLockDelayMs = 31; + ButtonRequest_U2FCounter = 32; + ButtonRequest_ConfirmEosAction = 33; + ButtonRequest_ConfirmEosBudget = 34; + ButtonRequest_ConfirmMemo = 35; +} + +/** + * Type of PIN request + * @used_in PinMatrixRequest + */ +enum PinMatrixRequestType { + PinMatrixRequestType_Current = 1; + PinMatrixRequestType_NewFirst = 2; + PinMatrixRequestType_NewSecond = 3; +} + +/** + * Structure representing BIP32 (hierarchical deterministic) node + * Used for imports of private key into the device and exporting public key out of device + * @used_in PublicKey + * @used_in LoadDevice + * @used_in DebugLinkState + * @used_in Storage + */ +message HDNodeType { + required uint32 depth = 1; + required uint32 fingerprint = 2; + required uint32 child_num = 3; + required bytes chain_code = 4; + optional bytes private_key = 5; + optional bytes public_key = 6; +} + +message HDNodePathType { + required HDNodeType node = 1; // BIP-32 node in deserialized form + repeated uint32 address_n = 2; // BIP-32 path to derive the key from node +} + +/** + * Structure representing Coin + * @used_in Features + */ +message CoinType { + optional string coin_name = 1; + optional string coin_shortcut = 2; + optional uint32 address_type = 3 [default=0]; + optional uint64 maxfee_kb = 4; + optional uint32 address_type_p2sh = 5 [default=5]; + //optional uint32 address_type_p2wpkh = 6 [default=6]; REMOVED + //optional uint32 address_type_p2wsh = 7 [default=10]; REMOVED + optional string signed_message_header = 8; + optional uint32 bip44_account_path = 9; + optional uint32 forkid = 12; + optional uint32 decimals = 13; + optional bytes contract_address = 14; + //optional bytes gas_limit = 15; REMOVED + optional uint32 xpub_magic = 16 [default=76067358]; + //optional uint32 xprv_magic = 17 [default=76066276]; REMOVED + optional bool segwit = 18; + optional bool force_bip143 = 19; + optional string curve_name = 20; + optional string cashaddr_prefix = 21; + optional string bech32_prefix = 22; + optional bool decred = 23; + // optional uint32 version_group_id = 24; REMOVED + optional uint32 xpub_magic_segwit_p2sh = 25; + optional uint32 xpub_magic_segwit_native = 26; + optional string nanoaddr_prefix = 27; +} + +/** + * Type of redeem script used in input + * @used_in TxInputType + */ +message MultisigRedeemScriptType { + repeated HDNodePathType pubkeys = 1; // pubkeys from multisig address (sorted lexicographically) + repeated bytes signatures = 2; // existing signatures for partially signed input + optional uint32 m = 3; // "m" from n, how many valid signatures is necessary for spending +} + +/** + * Structure representing transaction input + * @used_in TransactionType + */ +message TxInputType { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + required bytes prev_hash = 2; // hash of previous transaction output to spend by this input + required uint32 prev_index = 3; // index of previous output to spend + optional bytes script_sig = 4; // script signature, unset for tx to sign + optional uint32 sequence = 5 [default=0xffffffff]; // sequence + optional InputScriptType script_type = 6 [default=SPENDADDRESS]; // defines template of input script + optional MultisigRedeemScriptType multisig = 7; // Filled if input is going to spend multisig tx + optional uint64 amount = 8; // amount of previous transaction output (for segwit only) + optional uint32 decred_tree = 9; + optional uint32 decred_script_version = 10; +} + +/** + * Structure representing transaction output + * @used_in TransactionType + */ +message TxOutputType { + optional string address = 1; // target coin address in Base58 encoding + repeated uint32 address_n = 2; // BIP-32 path to derive the key from master node; has higher priority than "address" + required uint64 amount = 3; // amount to spend in satoshis + required OutputScriptType script_type = 4; // output script type + optional MultisigRedeemScriptType multisig = 5; // defines multisig address; script_type must be PAYTOMULTISIG + optional bytes op_return_data = 6; // defines op_return data; script_type must be PAYTOOPRETURN, amount must be 0 + optional OutputAddressType address_type = 7; // output address type + optional ExchangeType exchange_type = 8; // exchange type data + optional uint32 decred_script_version = 9; +} + +/** + * Structure representing compiled transaction output + * @used_in TransactionType + */ +message TxOutputBinType { + required uint64 amount = 1; + required bytes script_pubkey = 2; + optional uint32 decred_script_version = 3; +} + +/** + * Structure representing transaction + */ +message TransactionType { + optional uint32 version = 1; + repeated TxInputType inputs = 2; + repeated TxOutputBinType bin_outputs = 3; + repeated TxOutputType outputs = 5; + optional uint32 lock_time = 4; + optional uint32 inputs_cnt = 6; + optional uint32 outputs_cnt = 7; + optional bytes extra_data = 8; // only for Zcash + optional uint32 extra_data_len = 9; // only for Zcash + optional uint32 expiry = 10; // only for Decred, and Zcash + optional bool overwintered = 11; // only for Zcash + optional uint32 version_group_id = 12; // only for Zcash, nVersionGroupId when overwintered is set + optional uint32 branch_id = 13; // only for Zcash, BRANCH_ID when overwintered is set +} + +/** + * Structure representing raw transaction + * @used_in RawTxAck + */ +message RawTransactionType { + required bytes payload = 1; +} + +/** + * Structure representing request details + * @used_in TxRequest + */ +message TxRequestDetailsType { + optional uint32 request_index = 1; // device expects TxAck message from the computer + optional bytes tx_hash = 2; // tx_hash of requested transaction + optional uint32 extra_data_len = 3; // length of requested extra data + optional uint32 extra_data_offset = 4; // offset of requested extra data +} + +/** + * Structure representing serialized data + * @used_in TxRequest + */ +message TxRequestSerializedType { + optional uint32 signature_index = 1; // 'signature' field contains signed input of this index + optional bytes signature = 2; // signature of the signature_index input + optional bytes serialized_tx = 3; // part of serialized and signed transaction +} + +/** + * Structure representing identity data + * @used_in IdentityType + */ +message IdentityType { + optional string proto = 1; // proto part of URI + optional string user = 2; // user part of URI + optional string host = 3; // host part of URI + optional string port = 4; // port part of URI + optional string path = 5; // path part of URI + optional uint32 index = 6 [default=0]; // identity index +} + +/** + * Structure representing policy data + * @used_in ApplyPolicy + */ +message PolicyType { + optional string policy_name = 1; // name of policy + optional bool enabled = 2; // status of policy +} + +/** + * Structure representing exchange data + * @used_in TxOutputType + */ +message ExchangeType { + optional SignedExchangeResponse signed_exchange_response = 1; // exchange response + optional string withdrawal_coin_name = 2 [default='Bitcoin']; // coin type of the received funds + repeated uint32 withdrawal_address_n = 3; // BIP-32 path for received funds + repeated uint32 return_address_n = 4; // BIP-32 path for return address, assumed to be the + // same as the coin_name of the transaction + optional InputScriptType withdrawal_script_type = 5 [default=SPENDADDRESS]; + optional InputScriptType return_script_type = 6 [default=SPENDADDRESS]; +} diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py index a5f577a53743..2cea6bc3906d 100644 --- a/electrum/plugins/keepkey/keepkey.py +++ b/electrum/plugins/keepkey/keepkey.py @@ -75,10 +75,8 @@ def __init__(self, parent, config, name): try: from . import client - import keepkeylib - import keepkeylib.ckd_public - import keepkeylib.transport_hid - import keepkeylib.transport_webusb + from . import keepkeylib + from .keepkeylib import ckd_public, transport_hid, transport_webusb self.client_class = client.KeepKeyClient self.ckd_public = keepkeylib.ckd_public self.types = keepkeylib.client.types @@ -94,7 +92,7 @@ def __init__(self, parent, config, name): @runs_in_hwd_thread def enumerate(self): - from keepkeylib.transport_webusb import WebUsbTransport + from .keepkeylib.transport_webusb import WebUsbTransport results = [] for dev in WebUsbTransport.enumerate(): path = self._dev_to_str(dev) @@ -112,12 +110,12 @@ def _dev_to_str(dev: "usb1.USBDevice") -> str: @runs_in_hwd_thread def hid_transport(self, pair): - from keepkeylib.transport_hid import HidTransport + from .keepkeylib.transport_hid import HidTransport return HidTransport(pair) @runs_in_hwd_thread def webusb_transport(self, device): - from keepkeylib.transport_webusb import WebUsbTransport + from .keepkeylib.transport_webusb import WebUsbTransport for dev in WebUsbTransport.enumerate(): if device.path == self._dev_to_str(dev): return WebUsbTransport(dev) diff --git a/electrum/plugins/keepkey/keepkeylib/__init__.py b/electrum/plugins/keepkey/keepkeylib/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/electrum/plugins/keepkey/keepkeylib/ckd_public.py b/electrum/plugins/keepkey/keepkeylib/ckd_public.py new file mode 100644 index 000000000000..7f49d2855e64 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/ckd_public.py @@ -0,0 +1,125 @@ +import struct +import hmac +import hashlib + +import ecdsa +from ecdsa.util import string_to_number, number_to_string +from ecdsa.curves import SECP256k1 +from ecdsa.ellipticcurve import Point, INFINITY + +from . import tools +from . import types_pb2 as proto_types + +PRIME_DERIVATION_FLAG = 0x80000000 + +def point_to_pubkey(point): + order = SECP256k1.order + x_str = number_to_string(point.x(), order) + y_str = number_to_string(point.y(), order) + vk = x_str + y_str + return chr((ord(vk[63]) & 1) + 2) + vk[0:32] # To compressed key + +def sec_to_public_pair(pubkey): + """Convert a public key in sec binary format to a public pair.""" + x = string_to_number(pubkey[1:33]) + sec0 = pubkey[:1] + if sec0 not in (b'\2', b'\3'): + raise Exception("Compressed pubkey expected") + + def public_pair_for_x(generator, x, is_even): + curve = generator.curve() + p = curve.p() + alpha = (pow(x, 3, p) + curve.a() * x + curve.b()) % p + beta = ecdsa.numbertheory.square_root_mod_prime(alpha, p) + if is_even == bool(beta & 1): + return (x, p - beta) + return (x, beta) + + return public_pair_for_x(ecdsa.ecdsa.generator_secp256k1, x, is_even=(sec0 == b'\2')) + +def is_prime(n): + return (bool)(n & PRIME_DERIVATION_FLAG) + +def fingerprint(pubkey): + return string_to_number(tools.hash_160(pubkey)[:4]) + +def get_address(public_node, address_type): + return tools.public_key_to_bc_address(public_node.public_key, address_type) + +def public_ckd(public_node, n): + if not isinstance(n, list): + raise Exception('Parameter must be a list') + + node = proto_types.HDNodeType() + node.CopyFrom(public_node) + + for i in n: + node.CopyFrom(get_subnode(node, i)) + + return node + +def get_subnode(node, i): + # Public Child key derivation (CKD) algorithm of BIP32 + i_as_bytes = struct.pack(">L", i) + + if is_prime(i): + raise Exception("Prime derivation not supported") + + # Public derivation + data = node.public_key + i_as_bytes + + I64 = hmac.HMAC(key=node.chain_code, msg=data, digestmod=hashlib.sha512).digest() + I_left_as_exponent = string_to_number(I64[:32]) + + node_out = proto_types.HDNodeType() + node_out.depth = node.depth + 1 + node_out.child_num = i + node_out.chain_code = I64[32:] + node_out.fingerprint = fingerprint(node.public_key) + + # BIP32 magic converts old public key to new public point + x, y = sec_to_public_pair(node.public_key) + point = I_left_as_exponent * SECP256k1.generator + \ + Point(SECP256k1.curve, x, y, SECP256k1.order) + + if point == INFINITY: + raise Exception("Point cannot be INFINITY") + + # Convert public point to compressed public key + node_out.public_key = point_to_pubkey(point) + + return node_out + +def serialize(node, version=0x0488B21E): + s = '' + s += struct.pack('>I', version) + s += struct.pack('>B', node.depth) + s += struct.pack('>I', node.fingerprint) + s += struct.pack('>I', node.child_num) + s += node.chain_code + if node.private_key: + s += '\x00' + node.private_key + else: + s += node.public_key + s += tools.Hash(s)[:4] + return tools.b58encode(s) + +def deserialize(xpub): + data = tools.b58decode(xpub, None) + + if tools.Hash(data[:-4])[:4] != data[-4:]: + raise Exception("Checksum failed") + + node = proto_types.HDNodeType() + node.depth = struct.unpack('>B', data[4:5])[0] + node.fingerprint = struct.unpack('>I', data[5:9])[0] + node.child_num = struct.unpack('>I', data[9:13])[0] + node.chain_code = data[13:45] + + key = data[45:-4] + if key[0] == '\x00': + node.private_key = key[1:] + else: + node.public_key = key + + return node diff --git a/electrum/plugins/keepkey/keepkeylib/client.py b/electrum/plugins/keepkey/keepkeylib/client.py new file mode 100644 index 000000000000..c0fd0f91bed8 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/client.py @@ -0,0 +1,957 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2012-2016 Marek Palatinus +# Copyright (C) 2012-2016 Pavol Rusnak +# Copyright (C) 2016 Jochen Hoenicke +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This library 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . +# +# The script has been modified for KeepKey Device. + +from __future__ import print_function, absolute_import + +import os +import sys +import time +import binascii +import hashlib +import unicodedata +import json +import getpass +import copy + +from mnemonic import Mnemonic + +from . import tools +from . import mapping +from . import messages_pb2 as proto +from . import types_pb2 as types +from .debuglink import DebugLink + + +# try: +# from PIL import Image +# SCREENSHOT = True +# except: +# SCREENSHOT = False + +SCREENSHOT = False + +DEFAULT_CURVE = 'secp256k1' + +# monkeypatching: text formatting of protobuf messages +tools.monkeypatch_google_protobuf_text_format() + +def get_buttonrequest_value(code): + # Converts integer code to its string representation of ButtonRequestType + return [ k for k, v in types.ButtonRequestType.items() if v == code][0] + +def pprint(msg): + msg_class = msg.__class__.__name__ + msg_size = msg.ByteSize() + """ + msg_ser = msg.SerializeToString() + msg_id = mapping.get_type(msg) + msg_json = json.dumps(protobuf_json.pb2json(msg)) + """ + if isinstance(msg, proto.FirmwareUpload): + return "<%s> (%d bytes):\n" % (msg_class, msg_size) + else: + return "<%s> (%d bytes):\n%s" % (msg_class, msg_size, msg) + +def log(msg): + sys.stderr.write("%s\n" % msg.encode('utf-8')) + sys.stderr.flush() + +def log_cr(msg): + sys.stdout.write('\r%s' % msg.encode('utf-8')) + sys.stdout.flush() + +def format_mnemonic(word_pos, character_pos): + return "WORD %d: %s" % (word_pos, character_pos * '*') + +def getch(): + try: + import termios + except ImportError: + # Non-POSIX. Return msvcrt's (Windows') getch. + import msvcrt + return msvcrt.getch() + + # POSIX system. Create and return a getch that manipulates the tty. + import sys, tty + def _getch(): + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + return ch + + return _getch() + +class CallException(Exception): + def __init__(self, code, message): + super(CallException, self).__init__() + self.args = [code, message] + +class PinException(CallException): + pass + +class field(object): + # Decorator extracts single value from + # protobuf object. If the field is not + # present, raises an exception. + def __init__(self, field): + self.field = field + + def __call__(self, f): + def wrapped_f(*args, **kwargs): + ret = f(*args, **kwargs) + ret.HasField(self.field) + return getattr(ret, self.field) + return wrapped_f + +class expect(object): + # Decorator checks if the method + # returned one of expected protobuf messages + # or raises an exception + def __init__(self, *expected): + self.expected = expected + + def __call__(self, f): + def wrapped_f(*args, **kwargs): + ret = f(*args, **kwargs) + if not isinstance(ret, self.expected): + raise Exception("Got %s, expected %s" % (ret.__class__, self.expected)) + return ret + return wrapped_f + +def session(f): + # Decorator wraps a BaseClient method + # with session activation / deactivation + def wrapped_f(*args, **kwargs): + client = args[0] + try: + client.transport.session_begin() + return f(*args, **kwargs) + finally: + client.transport.session_end() + return wrapped_f + +def normalize_nfc(txt): + if sys.version_info[0] < 3: + if isinstance(txt, unicode): + return unicodedata.normalize('NFC', txt) + if isinstance(txt, str): + return unicodedata.normalize('NFC', txt.decode('utf-8')) + else: + if isinstance(txt, bytes): + return unicodedata.normalize('NFC', txt.decode('utf-8')) + if isinstance(txt, str): + return unicodedata.normalize('NFC', txt) + + raise Exception('unicode/str or bytes/str expected') + +class BaseClient(object): + # Implements very basic layer of sending raw protobuf + # messages to device and getting its response back. + def __init__(self, transport, **kwargs): + self.transport = transport + self.verbose = False + super(BaseClient, self).__init__() # *args, **kwargs) + + def cancel(self): + self.transport.write(proto.Cancel()) + + @session + def call_raw(self, msg): + self.transport.write(msg) + return self.transport.read_blocking() + + @session + def call(self, msg): + resp = self.call_raw(msg) + handler_name = "callback_%s" % resp.__class__.__name__ + handler = getattr(self, handler_name, None) + + if handler != None: + msg = handler(resp) + if msg == None: + raise Exception("Callback %s must return protobuf message, not None" % handler) + resp = self.call(msg) + + return resp + + def callback_Failure(self, msg): + if msg.code in (types.Failure_PinInvalid, + types.Failure_PinCancelled, types.Failure_PinExpected): + raise PinException(msg.code, msg.message) + + raise CallException(msg.code, msg.message) + + def close(self): + self.transport.close() + +class DebugWireMixin(object): + def call_raw(self, msg): + log("SENDING " + pprint(msg)) + resp = super(DebugWireMixin, self).call_raw(msg) + log("RECEIVED " + pprint(resp)) + return resp + +class TextUIMixin(object): + # This class demonstrates easy test-based UI + # integration between the device and wallet. + # You can implement similar functionality + # by implementing your own GuiMixin with + # graphical widgets for every type of these callbacks. + + def __init__(self, *args, **kwargs): + super(TextUIMixin, self).__init__(*args, **kwargs) + self.character_request_first_pass = True + + def callback_ButtonRequest(self, msg): + # log("Sending ButtonAck for %s " % get_buttonrequest_value(msg.code)) + return proto.ButtonAck() + + def callback_RecoveryMatrix(self, msg): + if self.recovery_matrix_first_pass: + self.recovery_matrix_first_pass = False + log("Use the numeric keypad to describe positions. For the word list use only left and right keys. The layout is:") + log(" 7 8 9 7 | 9") + log(" 4 5 6 4 | 6") + log(" 1 2 3 1 | 3") + while True: + character = getch() + if character in ('\x03', '\x04'): + return proto.Cancel() + + if character in ('\x08', '\x7f'): + return proto.WordAck(word='\x08') + + # ignore middle column if only 6 keys requested. + if (msg.type == types.WordRequestType_Matrix6 and + character in ('2', '5', '8')): + continue + + if (ord(character) >= ord('1') and ord(character) <= ord('9')): + return proto.WordAck(word=character) + + def callback_PinMatrixRequest(self, msg): + if msg.type == 1: + desc = 'current PIN' + elif msg.type == 2: + desc = 'new PIN' + elif msg.type == 3: + desc = 'new PIN again' + else: + desc = 'PIN' + + log("Use the numeric keypad to describe number positions. The layout is:") + log(" 7 8 9") + log(" 4 5 6") + log(" 1 2 3") + log("Please enter %s: " % desc) + pin = getpass.getpass('') + return proto.PinMatrixAck(pin=pin) + + def callback_PassphraseRequest(self, msg): + log("Passphrase required: ") + passphrase = getpass.getpass('') + log("Confirm your Passphrase: ") + if passphrase == getpass.getpass(''): + passphrase = normalize_nfc(passphrase) + return proto.PassphraseAck(passphrase=passphrase) + else: + log("Passphrase did not match! ") + exit() + + def callback_CharacterRequest(self, msg): + if self.character_request_first_pass: + self.character_request_first_pass = False + log("Use recovery cipher on device to input mnemonic. Words are autocompleted at 3 or 4 characters.") + log("(use spacebar to progress to next word after match, use backspace to correct bad character or word entries)") + + # format mnemonic for console + formatted_console = format_mnemonic(msg.word_pos + 1, msg.character_pos) + + # clear the runway before we display formatted mnemonic + log_cr(' ' * 14) + log_cr(formatted_console) + + while True: + character = getch().lower() + + # capture escape + if character in ('\x03', '\x04'): + return proto.Cancel() + + character_ascii = ord(character) + + if character_ascii >= 97 and character_ascii <= 122 \ + and msg.character_pos != 4: + # capture characters a-z + return proto.CharacterAck(character=character) + + elif character_ascii == 32 and msg.word_pos < 23 \ + and msg.character_pos >= 3: + # capture spaces + return proto.CharacterAck(character=' ') + + elif character_ascii == 8 or character_ascii == 127 \ + and (msg.word_pos > 0 or msg.character_pos > 0): + # capture backspaces + return proto.CharacterAck(delete=True) + + elif character_ascii == 13 and msg.word_pos in (11, 17, 23): + # capture returns + log("") + return proto.CharacterAck(done=True) + +class DebugLinkMixin(object): + # This class implements automatic responses + # and other functionality for unit tests + # for various callbacks, created in order + # to automatically pass unit tests. + # + # This mixing should be used only for purposes + # of unit testing, because it will fail to work + # without special DebugLink interface provided + # by the device. + + def __init__(self, *args, **kwargs): + super(DebugLinkMixin, self).__init__(*args, **kwargs) + self.debug = None + self.in_with_statement = 0 + self.button_wait = 0 + self.screenshot_id = 0 + + # Always press Yes and provide correct pin + self.setup_debuglink(True, True) + self.auto_button = True + + # Do not expect any specific response from device + self.expected_responses = None + + # Use blank passphrase + self.set_passphrase('') + + def close(self): + super(DebugLinkMixin, self).close() + if self.debug: + self.debug.close() + + def set_debuglink(self, debug_transport): + self.debug = DebugLink(debug_transport) + + def set_buttonwait(self, secs): + self.button_wait = secs + + def __enter__(self): + # For usage in with/expected_responses + self.in_with_statement += 1 + return self + + def __exit__(self, _type, value, traceback): + self.in_with_statement -= 1 + + if _type != None: + # Another exception raised + return False + + # return isinstance(value, TypeError) + # Evaluate missed responses in 'with' statement + if self.expected_responses != None and len(self.expected_responses): + raise Exception("Some of expected responses didn't come from device: %s" % \ + [ pprint(x) for x in self.expected_responses ]) + + # Cleanup + self.expected_responses = None + return False + + def set_expected_responses(self, expected): + if not self.in_with_statement: + raise Exception("Must be called inside 'with' statement") + self.expected_responses = expected + + def setup_debuglink(self, button, pin_correct): + self.button = button # True -> YES button, False -> NO button + self.pin_correct = pin_correct + + def set_passphrase(self, passphrase): + self.passphrase = normalize_nfc(passphrase) + + def set_mnemonic(self, mnemonic): + self.mnemonic = normalize_nfc(mnemonic).split(' ') + + def call_raw(self, msg): + + if SCREENSHOT and self.debug: + layout = self.debug.read_layout() + im = Image.new("RGB", (128, 64)) + pix = im.load() + for x in range(128): + for y in range(64): + rx, ry = 127 - x, 63 - y + if (ord(layout[rx + (ry / 8) * 128]) & (1 << (ry % 8))) > 0: + pix[x, y] = (255, 255, 255) + im.save('scr%05d.png' % self.screenshot_id) + self.screenshot_id += 1 + + resp = super(DebugLinkMixin, self).call_raw(msg) + self._check_request(resp) + return resp + + def _check_request(self, msg): + if self.expected_responses != None: + try: + expected = self.expected_responses.pop(0) + except IndexError: + raise CallException(types.Failure_Other, + "Got %s, but no message has been expected" % pprint(msg)) + + if msg.__class__ != expected.__class__: + raise CallException(types.Failure_Other, + "Expected %s, got %s" % (pprint(expected), pprint(msg))) + + fields = expected.ListFields() # only filled (including extensions) + for field, value in fields: + if not msg.HasField(field.name) or getattr(msg, field.name) != value: + raise CallException(types.Failure_Other, + "Expected %s, got %s" % (pprint(expected), pprint(msg))) + + def callback_ButtonRequest(self, msg): + if self.verbose: + log("ButtonRequest code: " + get_buttonrequest_value(msg.code)) + + if self.auto_button: + if self.verbose: + log("Pressing button " + str(self.button)) + if self.button_wait: + if self.verbose: + log("Waiting %d seconds " % self.button_wait) + time.sleep(self.button_wait) + self.debug.press_button(self.button) + + return proto.ButtonAck() + + def callback_PinMatrixRequest(self, msg): + if self.pin_correct: + pin = self.debug.read_pin_encoded() + else: + pin = '444222' + return proto.PinMatrixAck(pin=pin) + + def callback_PassphraseRequest(self, msg): + if self.verbose: + log("Provided passphrase: '%s'" % self.passphrase) + return proto.PassphraseAck(passphrase=self.passphrase) + + +class ProtocolMixin(object): + PRIME_DERIVATION_FLAG = 0x80000000 + VENDORS = ('keepkey.com',) + + def __init__(self, *args, **kwargs): + super(ProtocolMixin, self).__init__(*args, **kwargs) + self.init_device() + self.tx_api = None + + def set_tx_api(self, tx_api): + self.tx_api = tx_api + + def init_device(self): + self.features = expect(proto.Features)(self.call)(proto.Initialize()) + if str(self.features.vendor) not in self.VENDORS: + raise Exception("Unsupported device") + + def _get_local_entropy(self): + return os.urandom(32) + + def _convert_prime(self, n): + # Convert minus signs to uint32 with flag + return [ int(abs(x) | self.PRIME_DERIVATION_FLAG) if x < 0 else x for x in n ] + + @staticmethod + def expand_path(n): + # Convert string of bip32 path to list of uint32 integers with prime flags + # 0/-1/1' -> [0, 0x80000001, 0x80000001] + if not n: + return [] + + n = n.split('/') + + # m/a/b/c => a/b/c + if n[0] == 'm': + n = n[1:] + + # coin_name/a/b/c => 44'/SLIP44_constant'/a/b/c + # https://github.com/satoshilabs/slips/blob/master/slip-0044.md + coins = { + "Bitcoin": 0, + "Testnet": 1, + "Litecoin": 2, + "Dogecoin": 3, + "Dash": 5, + "Namecoin": 7, + "Bitsend": 91, + "Groestlcoin": 17, + "Zcash": 133, + "BitcoinCash": 145, + "Bitcore": 160, + "Megacoin": 217, + "Bitcloud": 218, + "Axe": 4242, + } + + if n[0] in coins: + n = ["44'", "%d'" % coins[n[0]] ] + n[1:] + + path = [] + for x in n: + prime = False + if x.endswith("'"): + x = x.replace('\'', '') + prime = True + if x.startswith('-'): + prime = True + + x = abs(int(x)) + + if prime: + x |= ProtocolMixin.PRIME_DERIVATION_FLAG + + path.append(x) + + return path + + @expect(proto.PublicKey) + def get_public_node(self, n, ecdsa_curve_name=DEFAULT_CURVE, show_display=False, coin_name=None, script_type=types.SPENDADDRESS): + n = self._convert_prime(n) + if not ecdsa_curve_name: + ecdsa_curve_name=DEFAULT_CURVE + return self.call(proto.GetPublicKey(address_n=n, ecdsa_curve_name=ecdsa_curve_name, show_display=show_display, coin_name=coin_name, script_type=script_type)) + + @field('address') + @expect(proto.Address) + def get_address(self, coin_name, n, show_display=False, multisig=None, script_type=types.SPENDADDRESS): + n = self._convert_prime(n) + if multisig: + return self.call(proto.GetAddress(address_n=n, coin_name=coin_name, show_display=show_display, multisig=multisig, script_type=script_type)) + else: + return self.call(proto.GetAddress(address_n=n, coin_name=coin_name, show_display=show_display, script_type=script_type)) + + @field('entropy') + @expect(proto.Entropy) + def get_entropy(self, size): + return self.call(proto.GetEntropy(size=size)) + + @field('message') + @expect(proto.Success) + def ping(self, msg, button_protection=False, pin_protection=False, passphrase_protection=False): + msg = proto.Ping(message=msg, + button_protection=button_protection, + pin_protection=pin_protection, + passphrase_protection=passphrase_protection) + return self.call(msg) + + def get_device_id(self): + return self.features.device_id + + @field('message') + @expect(proto.Success) + def apply_settings(self, label=None, language=None, use_passphrase=None, homescreen=None): + settings = proto.ApplySettings() + if label != None: + settings.label = label + if language: + settings.language = language + if use_passphrase != None: + settings.use_passphrase = use_passphrase + + out = self.call(settings) + self.init_device() # Reload Features + return out + + @field('message') + @expect(proto.Success) + def apply_policy(self, policy_name, enabled): + policy = types.PolicyType(policy_name=policy_name, enabled=enabled) + apply_policies = proto.ApplyPolicies(policy=[policy]) + + out = self.call(apply_policies) + self.init_device() # Reload Features + return out + + @field('message') + @expect(proto.Success) + def clear_session(self): + return self.call(proto.ClearSession()) + + @field('message') + @expect(proto.Success) + def change_pin(self, remove=False): + ret = self.call(proto.ChangePin(remove=remove)) + self.init_device() # Re-read features + return ret + + @expect(proto.MessageSignature) + def sign_message(self, coin_name, n, message, script_type=types.SPENDADDRESS): + n = self._convert_prime(n) + # Convert message to UTF8 NFC (seems to be a bitcoin-qt standard) + message = normalize_nfc(message).encode("utf-8") + return self.call(proto.SignMessage(coin_name=coin_name, address_n=n, message=message, script_type=script_type)) + + @expect(proto.SignedIdentity) + def sign_identity(self, identity, challenge_hidden, challenge_visual, ecdsa_curve_name=DEFAULT_CURVE): + return self.call(proto.SignIdentity(identity=identity, challenge_hidden=challenge_hidden, challenge_visual=challenge_visual, ecdsa_curve_name=ecdsa_curve_name)) + + + def verify_message(self, coin_name, address, signature, message): + # Convert message to UTF8 NFC (seems to be a bitcoin-qt standard) + message = normalize_nfc(message).encode("utf-8") + try: + resp = self.call(proto.VerifyMessage(address=address, signature=signature, message=message, coin_name=coin_name)) + except CallException as e: + resp = e + if isinstance(resp, proto.Success): + return True + return False + + @field('value') + @expect(proto.CipheredKeyValue) + def encrypt_keyvalue(self, n, key, value, ask_on_encrypt=True, ask_on_decrypt=True, iv=b''): + n = self._convert_prime(n) + return self.call(proto.CipherKeyValue(address_n=n, + key=key, + value=value, + encrypt=True, + ask_on_encrypt=ask_on_encrypt, + ask_on_decrypt=ask_on_decrypt, + iv=iv)) + + @field('value') + @expect(proto.CipheredKeyValue) + def decrypt_keyvalue(self, n, key, value, ask_on_encrypt=True, ask_on_decrypt=True, iv=b''): + n = self._convert_prime(n) + return self.call(proto.CipherKeyValue(address_n=n, + key=key, + value=value, + encrypt=False, + ask_on_encrypt=ask_on_encrypt, + ask_on_decrypt=ask_on_decrypt, + iv=iv)) + + def _prepare_sign_tx(self, coin_name, inputs, outputs): + tx = types.TransactionType() + tx.inputs.extend(inputs) + tx.outputs.extend(outputs) + + txes = {None: tx} + txes[b''] = tx + + force_bip143 = ['BitcoinGold', 'BitcoinCash', 'BitcoinSV'] + if coin_name in force_bip143: + return txes + + known_hashes = [] + for inp in inputs: + if inp.prev_hash in txes: + continue + + if inp.script_type in (types.SPENDP2SHWITNESS, + types.SPENDWITNESS): + continue + + if not self.tx_api: + raise Exception('TX_API not defined') + + prev_tx = self.tx_api.get_tx(binascii.hexlify(inp.prev_hash).decode('utf-8')) + txes[inp.prev_hash] = prev_tx + + return txes + + @session + def sign_tx(self, coin_name, inputs, outputs, version=None, lock_time=None, debug_processor=None): + + start = time.time() + txes = self._prepare_sign_tx(coin_name, inputs, outputs) + + # Prepare and send initial message + tx = proto.SignTx() + tx.inputs_count = len(inputs) + tx.outputs_count = len(outputs) + tx.coin_name = coin_name + if version is not None: + tx.version = version + if lock_time is not None: + tx.lock_time = lock_time + res = self.call(tx) + + # Prepare structure for signatures + signatures = [None] * len(inputs) + serialized_tx = b'' + + counter = 0 + while True: + counter += 1 + + if isinstance(res, proto.Failure): + raise CallException("Signing failed") + + if not isinstance(res, proto.TxRequest): + raise CallException("Unexpected message") + + # If there's some part of signed transaction, let's add it + if res.HasField('serialized') and res.serialized.HasField('serialized_tx'): + if self.verbose: + log("RECEIVED PART OF SERIALIZED TX (%d BYTES)" % len(res.serialized.serialized_tx)) + serialized_tx += res.serialized.serialized_tx + + if res.HasField('serialized') and res.serialized.HasField('signature_index'): + if signatures[res.serialized.signature_index] != None: + raise Exception("Signature for index %d already filled" % res.serialized.signature_index) + signatures[res.serialized.signature_index] = res.serialized.signature + + if res.request_type == types.TXFINISHED: + # Device didn't ask for more information, finish workflow + break + + # Device asked for one more information, let's process it. + if not res.details.tx_hash: + current_tx = txes[None] + else: + current_tx = txes[bytes(res.details.tx_hash)] + + if res.request_type == types.TXMETA: + msg = types.TransactionType() + msg.version = current_tx.version + msg.lock_time = current_tx.lock_time + msg.inputs_cnt = len(current_tx.inputs) + if res.details.tx_hash: + msg.outputs_cnt = len(current_tx.bin_outputs) + else: + msg.outputs_cnt = len(current_tx.outputs) + msg.extra_data_len = len(current_tx.extra_data) if current_tx.extra_data else 0 + res = self.call(proto.TxAck(tx=msg)) + continue + + elif res.request_type == types.TXINPUT: + msg = types.TransactionType() + msg.inputs.extend([current_tx.inputs[res.details.request_index], ]) + if debug_processor is not None: + # msg needs to be deep copied so when it's modified + # the other messages stay intact + from copy import deepcopy + msg = deepcopy(msg) + # If debug_processor function is provided, + # pass thru it the request and prepared response. + # This is useful for tests, see test_msg_signtx + msg = debug_processor(res, msg) + res = self.call(proto.TxAck(tx=msg)) + continue + + elif res.request_type == types.TXOUTPUT: + msg = types.TransactionType() + if res.details.tx_hash: + msg.bin_outputs.extend([current_tx.bin_outputs[res.details.request_index], ]) + else: + msg.outputs.extend([current_tx.outputs[res.details.request_index], ]) + + if debug_processor != None: + # msg needs to be deep copied so when it's modified + # the other messages stay intact + from copy import deepcopy + msg = deepcopy(msg) + # If debug_processor function is provided, + # pass thru it the request and prepared response. + # This is useful for tests, see test_msg_signtx + msg = debug_processor(res, msg) + + res = self.call(proto.TxAck(tx=msg)) + continue + + elif res.request_type == types.TXEXTRADATA: + o, l = res.details.extra_data_offset, res.details.extra_data_len + msg = types.TransactionType() + msg.extra_data = current_tx.extra_data[o:o + l] + res = self.call(proto.TxAck(tx=msg)) + continue + + if None in signatures: + raise Exception("Some signatures are missing!") + + if self.verbose: + log("SIGNED IN %.03f SECONDS, CALLED %d MESSAGES, %d BYTES" % \ + (time.time() - start, counter, len(serialized_tx))) + + return (signatures, serialized_tx) + + @field('message') + @expect(proto.Success) + def wipe_device(self): + ret = self.call(proto.WipeDevice()) + self.init_device() + return ret + + @field('message') + @expect(proto.Success) + def recovery_device(self, use_trezor_method, word_count, passphrase_protection, pin_protection, label, language): + if self.features.initialized: + raise Exception("Device is initialized already. Call wipe_device() and try again.") + if use_trezor_method: + raise Exception("Trezor-style recovery is no longer supported") + elif word_count not in (12, 18, 24): + raise Exception("Invalid word count. Use 12/18/24") + + res = self.call(proto.RecoveryDevice(word_count=int(word_count), + passphrase_protection=bool(passphrase_protection), + pin_protection=bool(pin_protection), + label=label, + language=language, + enforce_wordlist=True, + use_character_cipher=True)) + + self.init_device() + return res + + @field('message') + @expect(proto.Success) + @session + def reset_device(self, display_random, strength, passphrase_protection, pin_protection, label, language): + if self.features.initialized: + raise Exception("Device is initialized already. Call wipe_device() and try again.") + + # Begin with device reset workflow + msg = proto.ResetDevice(display_random=display_random, + strength=strength, + language=language, + passphrase_protection=bool(passphrase_protection), + pin_protection=bool(pin_protection), + label=label) + + resp = self.call(msg) + if not isinstance(resp, proto.EntropyRequest): + raise Exception("Invalid response, expected EntropyRequest") + + external_entropy = self._get_local_entropy() + if self.verbose: + log("Computer generated entropy: " + binascii.hexlify(external_entropy).decode('ascii')) + ret = self.call(proto.EntropyAck(entropy=external_entropy)) + self.init_device() + return ret + + @field('message') + @expect(proto.Success) + def load_device_by_mnemonic(self, mnemonic, pin, passphrase_protection, label, language, skip_checksum=False): + m = Mnemonic('english') + if not skip_checksum and not m.check(mnemonic): + raise Exception("Invalid mnemonic checksum") + + # Convert mnemonic to UTF8 NKFD + mnemonic = Mnemonic.normalize_string(mnemonic) + + # Convert mnemonic to ASCII stream + mnemonic = normalize_nfc(mnemonic) + + if self.features.initialized: + raise Exception("Device is initialized already. Call wipe_device() and try again.") + + resp = self.call(proto.LoadDevice(mnemonic=mnemonic, pin=pin, + passphrase_protection=passphrase_protection, + language=language, + label=label, + skip_checksum=skip_checksum)) + self.init_device() + return resp + + @field('message') + @expect(proto.Success) + def load_device_by_xprv(self, xprv, pin, passphrase_protection, label, language): + if self.features.initialized: + raise Exception("Device is initialized already. Call wipe_device() and try again.") + + if xprv[0:4] not in ('xprv', 'tprv'): + raise Exception("Unknown type of xprv") + + if len(xprv) < 100 and len(xprv) > 112: + raise Exception("Invalid length of xprv") + + node = types.HDNodeType() + data = binascii.hexlify(tools.b58decode(xprv, None)) + + if data[90:92] != b'00': + raise Exception("Contain invalid private key") + + checksum = binascii.hexlify(hashlib.sha256(hashlib.sha256(binascii.unhexlify(data[:156])).digest()).digest()[:4]) + if checksum != data[156:]: + raise Exception("Checksum doesn't match") + + # version 0488ade4 + # depth 00 + # fingerprint 00000000 + # child_num 00000000 + # chaincode 873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508 + # privkey 00e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35 + # checksum e77e9d71 + + node.depth = int(data[8:10], 16) + node.fingerprint = int(data[10:18], 16) + node.child_num = int(data[18:26], 16) + node.chain_code = binascii.unhexlify(data[26:90]) + node.private_key = binascii.unhexlify(data[92:156]) # skip 0x00 indicating privkey + + resp = self.call(proto.LoadDevice(node=node, + pin=pin, + passphrase_protection=passphrase_protection, + language=language, + label=label)) + self.init_device() + return resp + + def firmware_update(self, fp): + if self.features.bootloader_mode == False: + raise Exception("Device must be in bootloader mode") + + resp = self.call(proto.FirmwareErase()) + if isinstance(resp, proto.Failure) and resp.code == types.Failure_FirmwareError: + return False + + data = fp.read() + data_hash = hashlib.sha256(data).digest() + + resp = self.call(proto.FirmwareUpload(payload_hash=data_hash, payload=data)) + + if isinstance(resp, proto.Success): + return True + + elif isinstance(resp, proto.Failure) and resp.code == types.Failure_FirmwareError: + return False + + raise Exception("Unexpected result %s" % resp) + +class KeepKeyClient(ProtocolMixin, TextUIMixin, BaseClient): + pass + +class KeepKeyClientVerbose(ProtocolMixin, TextUIMixin, DebugWireMixin, BaseClient): + pass + +class KeepKeyDebuglinkClient(ProtocolMixin, DebugLinkMixin, BaseClient): + pass + +class KeepKeyDebuglinkClientVerbose(ProtocolMixin, DebugLinkMixin, DebugWireMixin, BaseClient): + pass diff --git a/electrum/plugins/keepkey/keepkeylib/debuglink.py b/electrum/plugins/keepkey/keepkeylib/debuglink.py new file mode 100644 index 000000000000..6b18baecf70d --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/debuglink.py @@ -0,0 +1,121 @@ +from __future__ import print_function + +from . import messages_pb2 as proto +from .transport import NotImplementedException + +def pin_info(pin, verbose): + if verbose: + print("Device asks for PIN %s" % pin) + +def button_press(yes_no, verbose): + if verbose: + print("User pressed", '"y"' if yes_no else '"n"') + +def pprint(msg): + return "<%s> (%d bytes):\n%s" % (msg.__class__.__name__, msg.ByteSize(), msg) + +class DebugLink(object): + def __init__(self, transport, pin_func=pin_info, button_func=button_press): + self.transport = transport + self.verbose = False + + self.pin_func = pin_func + self.button_func = button_func + + def log(self, what, why): + if self.verbose: + self.log(what, why) + + def close(self): + self.transport.close() + + def _call(self, msg, nowait=False): + self.log("DEBUGLINK SEND", pprint(msg)) + self.transport.write(msg) + if nowait: + return + ret = self.transport.read_blocking() + self.log("DEBUGLINK RECV", pprint(ret)) + return ret + + def read_pin(self): + obj = self._call(proto.DebugLinkGetState()) + self.log("Read PIN:", obj.pin) + self.log("Read matrix:", obj.matrix) + + return (obj.pin, obj.matrix) + + def read_pin_encoded(self): + pin, _ = self.read_pin() + pin_encoded = self.encode_pin(pin) + self.pin_func(pin_encoded, self.verbose) + return pin_encoded + + def encode_pin(self, pin): + _, matrix = self.read_pin() + + # Now we have real PIN and PIN matrix. + # We have to encode that into encoded pin, + # because application must send back positions + # on keypad, not a real PIN. + pin_encoded = ''.join([str(matrix.index(p) + 1) for p in pin]) + + self.log("Encoded PIN:", pin_encoded) + return pin_encoded + + def read_layout(self): + obj = self._call(proto.DebugLinkGetState()) + return obj.layout + + def read_mnemonic(self): + obj = self._call(proto.DebugLinkGetState()) + return obj.mnemonic + + def read_node(self): + obj = self._call(proto.DebugLinkGetState()) + return obj.node + + def read_recovery_word(self): + obj = self._call(proto.DebugLinkGetState()) + return (obj.recovery_fake_word, obj.recovery_word_pos) + + def read_reset_word(self): + obj = self._call(proto.DebugLinkGetState()) + return obj.reset_word + + def read_reset_entropy(self): + obj = self._call(proto.DebugLinkGetState()) + return obj.reset_entropy + + def read_passphrase_protection(self): + obj = self._call(proto.DebugLinkGetState()) + return obj.passphrase_protection + + def read_recovery_cipher(self): + obj = self._call(proto.DebugLinkGetState()) + return obj.recovery_cipher + + def read_recovery_auto_completed_word(self): + obj = self._call(proto.DebugLinkGetState()) + return obj.recovery_auto_completed_word + + def read_memory_hashes(self): + obj = self._call(proto.DebugLinkGetState()) + return (obj.firmware_hash, obj.storage_hash) + + def fill_config(self): + self._call(proto.DebugLinkFillConfig(), nowait=True) + + def press_button(self, yes_no): + self.log("Pressing", yes_no) + self.button_func(yes_no, self.verbose) + self._call(proto.DebugLinkDecision(yes_no=yes_no), nowait=True) + + def press_yes(self): + self.press_button(True) + + def press_no(self): + self.press_button(False) + + def stop(self): + self._call(proto.DebugLinkStop(), nowait=True) diff --git a/electrum/plugins/keepkey/keepkeylib/exchange.py b/electrum/plugins/keepkey/keepkeylib/exchange.py new file mode 100644 index 000000000000..10b8cf931a0d --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/exchange.py @@ -0,0 +1,46 @@ +from protobuf3.fields import Int64Field, MessageField, UInt64Field, BytesField, StringField +from protobuf3.message import Message + + +class ExchangeAddress(Message): + pass + + +class ExchangeResponseV2(Message): + pass + + +class SignedExchangeResponse(Message): + pass + + +class ExchangeResponse(Message): + pass + +ExchangeAddress.add_field('coin_type', StringField(field_number=1, optional=True)) +ExchangeAddress.add_field('address', StringField(field_number=2, optional=True)) +ExchangeAddress.add_field('dest_tag', StringField(field_number=3, optional=True)) +ExchangeAddress.add_field('rs_address', StringField(field_number=4, optional=True)) +ExchangeResponseV2.add_field('deposit_address', MessageField(field_number=1, optional=True, message_cls=ExchangeAddress)) +ExchangeResponseV2.add_field('deposit_amount', BytesField(field_number=2, optional=True)) +ExchangeResponseV2.add_field('expiration', Int64Field(field_number=3, optional=True)) +ExchangeResponseV2.add_field('quoted_rate', BytesField(field_number=4, optional=True)) +ExchangeResponseV2.add_field('withdrawal_address', MessageField(field_number=5, optional=True, message_cls=ExchangeAddress)) +ExchangeResponseV2.add_field('withdrawal_amount', BytesField(field_number=6, optional=True)) +ExchangeResponseV2.add_field('return_address', MessageField(field_number=7, optional=True, message_cls=ExchangeAddress)) +ExchangeResponseV2.add_field('api_key', BytesField(field_number=8, optional=True)) +ExchangeResponseV2.add_field('miner_fee', BytesField(field_number=9, optional=True)) +ExchangeResponseV2.add_field('order_id', BytesField(field_number=10, optional=True)) +SignedExchangeResponse.add_field('response', MessageField(field_number=1, optional=True, message_cls=ExchangeResponse)) +SignedExchangeResponse.add_field('signature', BytesField(field_number=2, optional=True)) +SignedExchangeResponse.add_field('responseV2', MessageField(field_number=3, optional=True, message_cls=ExchangeResponseV2)) +ExchangeResponse.add_field('deposit_address', MessageField(field_number=1, optional=True, message_cls=ExchangeAddress)) +ExchangeResponse.add_field('deposit_amount', UInt64Field(field_number=2, optional=True)) +ExchangeResponse.add_field('expiration', Int64Field(field_number=3, optional=True)) +ExchangeResponse.add_field('quoted_rate', UInt64Field(field_number=4, optional=True)) +ExchangeResponse.add_field('withdrawal_address', MessageField(field_number=5, optional=True, message_cls=ExchangeAddress)) +ExchangeResponse.add_field('withdrawal_amount', UInt64Field(field_number=6, optional=True)) +ExchangeResponse.add_field('return_address', MessageField(field_number=7, optional=True, message_cls=ExchangeAddress)) +ExchangeResponse.add_field('api_key', BytesField(field_number=8, optional=True)) +ExchangeResponse.add_field('miner_fee', UInt64Field(field_number=9, optional=True)) +ExchangeResponse.add_field('order_id', BytesField(field_number=10, optional=True)) diff --git a/electrum/plugins/keepkey/keepkeylib/exchange_pb2.py b/electrum/plugins/keepkey/keepkeylib/exchange_pb2.py new file mode 100644 index 000000000000..efe54d0c4e01 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/exchange_pb2.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: exchange.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x65xchange.proto\"G\n\x0f\x45xchangeAddress\x12\x11\n\tcoin_type\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\t\x12\x10\n\x08\x64\x65st_tag\x18\x03 \x01(\t\"\xcc\x02\n\x12\x45xchangeResponseV2\x12)\n\x0f\x64\x65posit_address\x18\x01 \x01(\x0b\x32\x10.ExchangeAddress\x12\x16\n\x0e\x64\x65posit_amount\x18\x02 \x01(\x0c\x12\x12\n\nexpiration\x18\x03 \x01(\x03\x12\x13\n\x0bquoted_rate\x18\x04 \x01(\x0c\x12,\n\x12withdrawal_address\x18\x05 \x01(\x0b\x32\x10.ExchangeAddress\x12\x19\n\x11withdrawal_amount\x18\x06 \x01(\x0c\x12(\n\x0ereturn_address\x18\x07 \x01(\x0b\x32\x10.ExchangeAddress\x12\x0f\n\x07\x61pi_key\x18\x08 \x01(\x0c\x12\x11\n\tminer_fee\x18\t \x01(\x0c\x12\x10\n\x08order_id\x18\n \x01(\x0c\x12!\n\x04type\x18\x0b \x01(\x0e\x32\n.OrderType:\x07Precise\"T\n\x16SignedExchangeResponse\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\'\n\nresponseV2\x18\x03 \x01(\x0b\x32\x13.ExchangeResponseV2*#\n\tOrderType\x12\x0b\n\x07Precise\x10\x00\x12\t\n\x05Quick\x10\x01\x42.\n\x1b\x63om.keepkey.device-protocolB\x0fKeepKeyExchange') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'exchange_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'\n\033com.keepkey.device-protocolB\017KeepKeyExchange' + _ORDERTYPE._serialized_start=512 + _ORDERTYPE._serialized_end=547 + _EXCHANGEADDRESS._serialized_start=18 + _EXCHANGEADDRESS._serialized_end=89 + _EXCHANGERESPONSEV2._serialized_start=92 + _EXCHANGERESPONSEV2._serialized_end=424 + _SIGNEDEXCHANGERESPONSE._serialized_start=426 + _SIGNEDEXCHANGERESPONSE._serialized_end=510 +# @@protoc_insertion_point(module_scope) diff --git a/electrum/plugins/keepkey/keepkeylib/filecache.py b/electrum/plugins/keepkey/keepkeylib/filecache.py new file mode 100644 index 000000000000..69625b4ab9a9 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/filecache.py @@ -0,0 +1,187 @@ +''' +filecache + +filecache is a decorator which saves the return value of functions even +after the interpreter dies. For example this is useful on functions that download +and parse webpages. All you need to do is specify how long +the return values should be cached (use seconds, like time.sleep). + +USAGE: + + from filecache import filecache + + @filecache(24 * 60 * 60) + def time_consuming_function(args): + # etc + + @filecache(filecache.YEAR) + def another_function(args): + # etc + + +NOTE: All arguments of the decorated function and the return value need to be + picklable for this to work. + +NOTE: The cache isn't automatically cleaned, it is only overwritten. If your + function can receive many different arguments that rarely repeat, your + cache may forever grow. One day I might add a feature that once in every + 100 calls scans the db for outdated stuff and erases. + +NOTE: This is less useful on methods of a class because the instance (self) + is cached, and if the instance isn't the same, the cache isn't used. This + makes sense because class methods are affected by changes in whatever + is attached to self. + +Tested on python 2.7 and 3.1 + +License: BSD, do what you wish with this. Could be awesome to hear if you found +it useful and/or you have suggestions. ubershmekel at gmail + + +A trick to invalidate a single value: + + @filecache.filecache + def somefunc(x, y, z): + return x * y * z + + del somefunc._db[filecache._args_key(somefunc, (1,2,3), {})] + # or just iterate of somefunc._db (it's a shelve, like a dict) to find the right key. + + +''' + + +import collections as _collections +import datetime as _datetime +import functools as _functools +import inspect as _inspect +import os as _os +import pickle as _pickle +import shelve as _shelve +import sys as _sys +import time as _time +import traceback as _traceback +import types +import atexit + +_retval = _collections.namedtuple('_retval', 'timesig data') +_SRC_DIR = _os.path.dirname(_os.path.abspath(__file__)) + +SECOND = 1 +MINUTE = 60 * SECOND +HOUR = 60 * MINUTE +DAY = 24 * HOUR +WEEK = 7 * DAY +MONTH = 30 * DAY +YEAR = 365 * DAY +FOREVER = None + +OPEN_DBS = dict() + +def _get_cache_name(function): + """ + returns a name for the module's cache db. + """ + module_name = _inspect.getfile(function) + module_name = _os.path.abspath(module_name) + cache_name = module_name + + # fix for '' or '' in exec or interpreter usage. + cache_name = cache_name.replace('<', '_lt_') + cache_name = cache_name.replace('>', '_gt_') + + tmpdir = _os.getenv('TMPDIR') or _os.getenv('TEMP') or _os.getenv('TMP') + if tmpdir: + cache_name = tmpdir + '/filecache_' + cache_name.replace(_os.sep, '@') + + cache_name += '.cache' + return cache_name + + +def _log_error(error_str): + try: + error_log_fname = _os.path.join(_SRC_DIR, 'filecache.err.log') + if _os.path.isfile(error_log_fname): + fhand = open(error_log_fname, 'a') + else: + fhand = open(error_log_fname, 'w') + fhand.write('[%s] %s\r\n' % (_datetime.datetime.now().isoformat(), error_str)) + fhand.close() + except Exception: + pass + +def _args_key(function, args, kwargs): + arguments = (args, kwargs) + # Check if you have a valid, cached answer, and return it. + # Sadly this is python version dependant + if _sys.version_info[0] == 2: + arguments_pickle = _pickle.dumps(arguments) + else: + # NOTE: protocol=0 so it's ascii, this is crucial for py3k + # because shelve only works with proper strings. + # Otherwise, we'd get an exception because + # function.__name__ is str but dumps returns bytes. + arguments_pickle = _pickle.dumps(arguments, protocol=0).decode('ascii') + + key = function.__name__ + arguments_pickle + return key + +def filecache(seconds_of_validity=None, fail_silently=False): + ''' + filecache is called and the decorator should be returned. + ''' + def filecache_decorator(function): + @_functools.wraps(function) + def function_with_cache(*args, **kwargs): + try: + key = _args_key(function, args, kwargs) + + if key in function._db: + rv = function._db[key] + if seconds_of_validity is None or _time.time() - rv.timesig < seconds_of_validity: + return rv.data + except Exception: + # in any case of failure, don't let filecache break the program + error_str = _traceback.format_exc() + _log_error(error_str) + if not fail_silently: + raise + + retval = function(*args, **kwargs) + + # store in cache + # NOTE: no need to _db.sync() because there was no mutation + # NOTE: it's importatnt to do _db.sync() because otherwise the cache doesn't survive Ctrl-Break! + try: + function._db[key] = _retval(_time.time(), retval) + function._db.sync() + except Exception: + # in any case of failure, don't let filecache break the program + error_str = _traceback.format_exc() + _log_error(error_str) + if not fail_silently: + raise + + return retval + + # make sure cache is loaded + if not hasattr(function, '_db'): + cache_name = _get_cache_name(function) + if cache_name in OPEN_DBS: + function._db = OPEN_DBS[cache_name] + else: + function._db = _shelve.open(cache_name) + OPEN_DBS[cache_name] = function._db + atexit.register(function._db.close) + + function_with_cache._db = function._db + + return function_with_cache + + if type(seconds_of_validity) == types.FunctionType: + # support for when people use '@filecache.filecache' instead of '@filecache.filecache()' + func = seconds_of_validity + seconds_of_validity = None + return filecache_decorator(func) + + return filecache_decorator diff --git a/electrum/plugins/keepkey/keepkeylib/mapping.py b/electrum/plugins/keepkey/keepkeylib/mapping.py new file mode 100644 index 000000000000..3496d8ada238 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/mapping.py @@ -0,0 +1,33 @@ +from . import messages_pb2 as proto + +map_type_to_class = {} +map_class_to_type = {} + +def build_map(): + for msg_type, i in proto.MessageType.items(): + msg_name = msg_type.replace('MessageType_', '') + msg_class = getattr(proto, msg_name) + + map_type_to_class[i] = msg_class + map_class_to_type[msg_class] = i + +def get_type(msg): + return map_class_to_type[msg.__class__] + + +def get_class(t): + return map_type_to_class[t] + +def check_missing(): + from google.protobuf import reflection + + types = [getattr(proto, item) for item in dir(proto) + if issubclass(getattr(proto, item).__class__, reflection.GeneratedProtocolMessageType)] + + missing = list(set(types) - set(map_type_to_class.values())) + + if len(missing): + raise Exception("Following protobuf messages are not defined in mapping: %s" % missing) + +build_map() +check_missing() diff --git a/electrum/plugins/keepkey/keepkeylib/messages_pb2.py b/electrum/plugins/keepkey/keepkeylib/messages_pb2.py new file mode 100644 index 000000000000..1783a3cdc205 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/messages_pb2.py @@ -0,0 +1,283 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: messages.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from . import types_pb2 as types__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x1a\x0btypes.proto\"\x0c\n\nInitialize\"\r\n\x0bGetFeatures\"\xf0\x03\n\x08\x46\x65\x61tures\x12\x0e\n\x06vendor\x18\x01 \x01(\t\x12\x15\n\rmajor_version\x18\x02 \x01(\r\x12\x15\n\rminor_version\x18\x03 \x01(\r\x12\x15\n\rpatch_version\x18\x04 \x01(\r\x12\x17\n\x0f\x62ootloader_mode\x18\x05 \x01(\x08\x12\x11\n\tdevice_id\x18\x06 \x01(\t\x12\x16\n\x0epin_protection\x18\x07 \x01(\x08\x12\x1d\n\x15passphrase_protection\x18\x08 \x01(\x08\x12\x10\n\x08language\x18\t \x01(\t\x12\r\n\x05label\x18\n \x01(\t\x12\x18\n\x05\x63oins\x18\x0b \x03(\x0b\x32\t.CoinType\x12\x13\n\x0binitialized\x18\x0c \x01(\x08\x12\x10\n\x08revision\x18\r \x01(\x0c\x12\x17\n\x0f\x62ootloader_hash\x18\x0e \x01(\x0c\x12\x10\n\x08imported\x18\x0f \x01(\x08\x12\x12\n\npin_cached\x18\x10 \x01(\x08\x12\x19\n\x11passphrase_cached\x18\x11 \x01(\x08\x12\x1d\n\x08policies\x18\x12 \x03(\x0b\x32\x0b.PolicyType\x12\r\n\x05model\x18\x15 \x01(\t\x12\x18\n\x10\x66irmware_variant\x18\x16 \x01(\t\x12\x15\n\rfirmware_hash\x18\x17 \x01(\x0c\x12\x11\n\tno_backup\x18\x18 \x01(\x08\"*\n\x0cGetCoinTable\x12\r\n\x05start\x18\x01 \x01(\r\x12\x0b\n\x03\x65nd\x18\x02 \x01(\r\"L\n\tCoinTable\x12\x18\n\x05table\x18\x01 \x03(\x0b\x32\t.CoinType\x12\x11\n\tnum_coins\x18\x02 \x01(\r\x12\x12\n\nchunk_size\x18\x03 \x01(\r\"\x0e\n\x0c\x43learSession\"y\n\rApplySettings\x12\x10\n\x08language\x18\x01 \x01(\t\x12\r\n\x05label\x18\x02 \x01(\t\x12\x16\n\x0euse_passphrase\x18\x03 \x01(\x08\x12\x1a\n\x12\x61uto_lock_delay_ms\x18\x04 \x01(\r\x12\x13\n\x0bu2f_counter\x18\x05 \x01(\r\"\x1b\n\tChangePin\x12\x0e\n\x06remove\x18\x01 \x01(\x08\"i\n\x04Ping\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x19\n\x11\x62utton_protection\x18\x02 \x01(\x08\x12\x16\n\x0epin_protection\x18\x03 \x01(\x08\x12\x1d\n\x15passphrase_protection\x18\x04 \x01(\x08\"\x1a\n\x07Success\x12\x0f\n\x07message\x18\x01 \x01(\t\"6\n\x07\x46\x61ilure\x12\x1a\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x0c.FailureType\x12\x0f\n\x07message\x18\x02 \x01(\t\"?\n\rButtonRequest\x12 \n\x04\x63ode\x18\x01 \x01(\x0e\x32\x12.ButtonRequestType\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\t\"\x0b\n\tButtonAck\"7\n\x10PinMatrixRequest\x12#\n\x04type\x18\x01 \x01(\x0e\x32\x15.PinMatrixRequestType\"\x1b\n\x0cPinMatrixAck\x12\x0b\n\x03pin\x18\x01 \x02(\t\"\x08\n\x06\x43\x61ncel\"\x13\n\x11PassphraseRequest\"#\n\rPassphraseAck\x12\x12\n\npassphrase\x18\x01 \x02(\t\"\x1a\n\nGetEntropy\x12\x0c\n\x04size\x18\x01 \x02(\r\"\x1a\n\x07\x45ntropy\x12\x0f\n\x07\x65ntropy\x18\x01 \x02(\x0c\"\xa2\x01\n\x0cGetPublicKey\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x18\n\x10\x65\x63\x64sa_curve_name\x18\x02 \x01(\t\x12\x14\n\x0cshow_display\x18\x03 \x01(\x08\x12\x1a\n\tcoin_name\x18\x04 \x01(\t:\x07\x42itcoin\x12\x33\n\x0bscript_type\x18\x05 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS\"4\n\tPublicKey\x12\x19\n\x04node\x18\x01 \x02(\x0b\x32\x0b.HDNodeType\x12\x0c\n\x04xpub\x18\x02 \x01(\t\"\xb3\x01\n\nGetAddress\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x1a\n\tcoin_name\x18\x02 \x01(\t:\x07\x42itcoin\x12\x14\n\x0cshow_display\x18\x03 \x01(\x08\x12+\n\x08multisig\x18\x04 \x01(\x0b\x32\x19.MultisigRedeemScriptType\x12\x33\n\x0bscript_type\x18\x05 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS\"\x1a\n\x07\x41\x64\x64ress\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x02(\t\"\x0c\n\nWipeDevice\"\xbb\x01\n\nLoadDevice\x12\x10\n\x08mnemonic\x18\x01 \x01(\t\x12\x19\n\x04node\x18\x02 \x01(\x0b\x32\x0b.HDNodeType\x12\x0b\n\x03pin\x18\x03 \x01(\t\x12\x1d\n\x15passphrase_protection\x18\x04 \x01(\x08\x12\x19\n\x08language\x18\x05 \x01(\t:\x07\x65nglish\x12\r\n\x05label\x18\x06 \x01(\t\x12\x15\n\rskip_checksum\x18\x07 \x01(\x08\x12\x13\n\x0bu2f_counter\x18\x08 \x01(\r\"\xe1\x01\n\x0bResetDevice\x12\x16\n\x0e\x64isplay_random\x18\x01 \x01(\x08\x12\x15\n\x08strength\x18\x02 \x01(\r:\x03\x32\x35\x36\x12\x1d\n\x15passphrase_protection\x18\x03 \x01(\x08\x12\x16\n\x0epin_protection\x18\x04 \x01(\x08\x12\x19\n\x08language\x18\x05 \x01(\t:\x07\x65nglish\x12\r\n\x05label\x18\x06 \x01(\t\x12\x11\n\tno_backup\x18\x07 \x01(\x08\x12\x1a\n\x12\x61uto_lock_delay_ms\x18\x08 \x01(\r\x12\x13\n\x0bu2f_counter\x18\t \x01(\r\"\x10\n\x0e\x45ntropyRequest\"\x1d\n\nEntropyAck\x12\x0f\n\x07\x65ntropy\x18\x01 \x01(\x0c\"\xff\x01\n\x0eRecoveryDevice\x12\x12\n\nword_count\x18\x01 \x01(\r\x12\x1d\n\x15passphrase_protection\x18\x02 \x01(\x08\x12\x16\n\x0epin_protection\x18\x03 \x01(\x08\x12\x19\n\x08language\x18\x04 \x01(\t:\x07\x65nglish\x12\r\n\x05label\x18\x05 \x01(\t\x12\x18\n\x10\x65nforce_wordlist\x18\x06 \x01(\x08\x12\x1c\n\x14use_character_cipher\x18\x07 \x01(\x08\x12\x1a\n\x12\x61uto_lock_delay_ms\x18\x08 \x01(\r\x12\x13\n\x0bu2f_counter\x18\t \x01(\r\x12\x0f\n\x07\x64ry_run\x18\n \x01(\x08\"\r\n\x0bWordRequest\"\x17\n\x07WordAck\x12\x0c\n\x04word\x18\x01 \x02(\t\";\n\x10\x43haracterRequest\x12\x10\n\x08word_pos\x18\x01 \x02(\r\x12\x15\n\rcharacter_pos\x18\x02 \x02(\r\"?\n\x0c\x43haracterAck\x12\x11\n\tcharacter\x18\x01 \x01(\t\x12\x0e\n\x06\x64\x65lete\x18\x02 \x01(\x08\x12\x0c\n\x04\x64one\x18\x03 \x01(\x08\"\x82\x01\n\x0bSignMessage\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07message\x18\x02 \x02(\x0c\x12\x1a\n\tcoin_name\x18\x03 \x01(\t:\x07\x42itcoin\x12\x33\n\x0bscript_type\x18\x04 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS\"`\n\rVerifyMessage\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x0f\n\x07message\x18\x03 \x01(\x0c\x12\x1a\n\tcoin_name\x18\x04 \x01(\t:\x07\x42itcoin\"6\n\x10MessageSignature\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x11\n\tsignature\x18\x02 \x01(\x0c\"v\n\x0e\x45ncryptMessage\x12\x0e\n\x06pubkey\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x02 \x01(\x0c\x12\x14\n\x0c\x64isplay_only\x18\x03 \x01(\x08\x12\x11\n\taddress_n\x18\x04 \x03(\r\x12\x1a\n\tcoin_name\x18\x05 \x01(\t:\x07\x42itcoin\"@\n\x10\x45ncryptedMessage\x12\r\n\x05nonce\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x02 \x01(\x0c\x12\x0c\n\x04hmac\x18\x03 \x01(\x0c\"Q\n\x0e\x44\x65\x63ryptMessage\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\r\n\x05nonce\x18\x02 \x01(\x0c\x12\x0f\n\x07message\x18\x03 \x01(\x0c\x12\x0c\n\x04hmac\x18\x04 \x01(\x0c\"4\n\x10\x44\x65\x63ryptedMessage\x12\x0f\n\x07message\x18\x01 \x01(\x0c\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\t\"\x8c\x01\n\x0e\x43ipherKeyValue\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\x0c\x12\x0f\n\x07\x65ncrypt\x18\x04 \x01(\x08\x12\x16\n\x0e\x61sk_on_encrypt\x18\x05 \x01(\x08\x12\x16\n\x0e\x61sk_on_decrypt\x18\x06 \x01(\x08\x12\n\n\x02iv\x18\x07 \x01(\x0c\"!\n\x10\x43ipheredKeyValue\x12\r\n\x05value\x18\x01 \x01(\x0c\"\xce\x01\n\x06SignTx\x12\x15\n\routputs_count\x18\x01 \x02(\r\x12\x14\n\x0cinputs_count\x18\x02 \x02(\r\x12\x1a\n\tcoin_name\x18\x03 \x01(\t:\x07\x42itcoin\x12\x12\n\x07version\x18\x04 \x01(\r:\x01\x31\x12\x14\n\tlock_time\x18\x05 \x01(\r:\x01\x30\x12\x0e\n\x06\x65xpiry\x18\x06 \x01(\r\x12\x14\n\x0coverwintered\x18\x07 \x01(\x08\x12\x18\n\x10version_group_id\x18\x08 \x01(\r\x12\x11\n\tbranch_id\x18\n \x01(\r\"\x85\x01\n\tTxRequest\x12\"\n\x0crequest_type\x18\x01 \x01(\x0e\x32\x0c.RequestType\x12&\n\x07\x64\x65tails\x18\x02 \x01(\x0b\x32\x15.TxRequestDetailsType\x12,\n\nserialized\x18\x03 \x01(\x0b\x32\x18.TxRequestSerializedType\"%\n\x05TxAck\x12\x1c\n\x02tx\x18\x01 \x01(\x0b\x32\x10.TransactionType\"+\n\x08RawTxAck\x12\x1f\n\x02tx\x18\x01 \x01(\x0b\x32\x13.RawTransactionType\"}\n\x0cSignIdentity\x12\x1f\n\x08identity\x18\x01 \x01(\x0b\x32\r.IdentityType\x12\x18\n\x10\x63hallenge_hidden\x18\x02 \x01(\x0c\x12\x18\n\x10\x63hallenge_visual\x18\x03 \x01(\t\x12\x18\n\x10\x65\x63\x64sa_curve_name\x18\x04 \x01(\t\"H\n\x0eSignedIdentity\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x12\n\npublic_key\x18\x02 \x01(\x0c\x12\x11\n\tsignature\x18\x03 \x01(\x0c\",\n\rApplyPolicies\x12\x1b\n\x06policy\x18\x01 \x03(\x0b\x32\x0b.PolicyType\"?\n\tFlashHash\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\r\x12\x0e\n\x06length\x18\x02 \x01(\r\x12\x11\n\tchallenge\x18\x03 \x01(\x0c\":\n\nFlashWrite\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\r\n\x05\x65rase\x18\x03 \x01(\x08\"!\n\x11\x46lashHashResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"5\n\x12\x44\x65\x62ugLinkFlashDump\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\r\x12\x0e\n\x06length\x18\x02 \x01(\r\"*\n\x1a\x44\x65\x62ugLinkFlashDumpResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x0b\n\tSoftReset\"\x0f\n\rFirmwareErase\"7\n\x0e\x46irmwareUpload\x12\x14\n\x0cpayload_hash\x18\x01 \x02(\x0c\x12\x0f\n\x07payload\x18\x02 \x02(\x0c\"#\n\x11\x44\x65\x62ugLinkDecision\x12\x0e\n\x06yes_no\x18\x01 \x02(\x08\"\x13\n\x11\x44\x65\x62ugLinkGetState\"\xd7\x02\n\x0e\x44\x65\x62ugLinkState\x12\x0e\n\x06layout\x18\x01 \x01(\x0c\x12\x0b\n\x03pin\x18\x02 \x01(\t\x12\x0e\n\x06matrix\x18\x03 \x01(\t\x12\x10\n\x08mnemonic\x18\x04 \x01(\t\x12\x19\n\x04node\x18\x05 \x01(\x0b\x32\x0b.HDNodeType\x12\x1d\n\x15passphrase_protection\x18\x06 \x01(\x08\x12\x12\n\nreset_word\x18\x07 \x01(\t\x12\x15\n\rreset_entropy\x18\x08 \x01(\x0c\x12\x1a\n\x12recovery_fake_word\x18\t \x01(\t\x12\x19\n\x11recovery_word_pos\x18\n \x01(\r\x12\x17\n\x0frecovery_cipher\x18\x0b \x01(\t\x12$\n\x1crecovery_auto_completed_word\x18\x0c \x01(\t\x12\x15\n\rfirmware_hash\x18\r \x01(\x0c\x12\x14\n\x0cstorage_hash\x18\x0e \x01(\x0c\"\x0f\n\rDebugLinkStop\";\n\x0c\x44\x65\x62ugLinkLog\x12\r\n\x05level\x18\x01 \x01(\r\x12\x0e\n\x06\x62ucket\x18\x02 \x01(\t\x12\x0c\n\x04text\x18\x03 \x01(\t\"\x15\n\x13\x44\x65\x62ugLinkFillConfig*\x88\x12\n\x0bMessageType\x12 \n\x16MessageType_Initialize\x10\x00\x1a\x04\x90\xb5\x18\x01\x12\x1a\n\x10MessageType_Ping\x10\x01\x1a\x04\x90\xb5\x18\x01\x12\x1d\n\x13MessageType_Success\x10\x02\x1a\x04\x98\xb5\x18\x01\x12\x1d\n\x13MessageType_Failure\x10\x03\x1a\x04\x98\xb5\x18\x01\x12\x1f\n\x15MessageType_ChangePin\x10\x04\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_WipeDevice\x10\x05\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_FirmwareErase\x10\x06\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_FirmwareUpload\x10\x07\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_GetEntropy\x10\t\x1a\x04\x90\xb5\x18\x01\x12\x1d\n\x13MessageType_Entropy\x10\n\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_GetPublicKey\x10\x0b\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_PublicKey\x10\x0c\x1a\x04\x98\xb5\x18\x01\x12 \n\x16MessageType_LoadDevice\x10\r\x1a\x04\x90\xb5\x18\x01\x12!\n\x17MessageType_ResetDevice\x10\x0e\x1a\x04\x90\xb5\x18\x01\x12\x1c\n\x12MessageType_SignTx\x10\x0f\x1a\x04\x90\xb5\x18\x01\x12\x1e\n\x14MessageType_Features\x10\x11\x1a\x04\x98\xb5\x18\x01\x12&\n\x1cMessageType_PinMatrixRequest\x10\x12\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_PinMatrixAck\x10\x13\x1a\x04\x90\xb5\x18\x01\x12\x1c\n\x12MessageType_Cancel\x10\x14\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_TxRequest\x10\x15\x1a\x04\x98\xb5\x18\x01\x12\x1b\n\x11MessageType_TxAck\x10\x16\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_CipherKeyValue\x10\x17\x1a\x04\x90\xb5\x18\x01\x12\"\n\x18MessageType_ClearSession\x10\x18\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_ApplySettings\x10\x19\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_ButtonRequest\x10\x1a\x1a\x04\x98\xb5\x18\x01\x12\x1f\n\x15MessageType_ButtonAck\x10\x1b\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_GetAddress\x10\x1d\x1a\x04\x90\xb5\x18\x01\x12\x1d\n\x13MessageType_Address\x10\x1e\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_EntropyRequest\x10#\x1a\x04\x98\xb5\x18\x01\x12 \n\x16MessageType_EntropyAck\x10$\x1a\x04\x90\xb5\x18\x01\x12!\n\x17MessageType_SignMessage\x10&\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_VerifyMessage\x10\'\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_MessageSignature\x10(\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1dMessageType_PassphraseRequest\x10)\x1a\x04\x98\xb5\x18\x01\x12#\n\x19MessageType_PassphraseAck\x10*\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_RecoveryDevice\x10-\x1a\x04\x90\xb5\x18\x01\x12!\n\x17MessageType_WordRequest\x10.\x1a\x04\x98\xb5\x18\x01\x12\x1d\n\x13MessageType_WordAck\x10/\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_CipheredKeyValue\x10\x30\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_EncryptMessage\x10\x31\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_EncryptedMessage\x10\x32\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_DecryptMessage\x10\x33\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_DecryptedMessage\x10\x34\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_SignIdentity\x10\x35\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_SignedIdentity\x10\x36\x1a\x04\x98\xb5\x18\x01\x12!\n\x17MessageType_GetFeatures\x10\x37\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_CharacterRequest\x10P\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_CharacterAck\x10Q\x1a\x04\x90\xb5\x18\x01\x12\x1e\n\x14MessageType_RawTxAck\x10R\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_ApplyPolicies\x10S\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_FlashHash\x10T\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_FlashWrite\x10U\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1dMessageType_FlashHashResponse\x10V\x1a\x04\x98\xb5\x18\x01\x12(\n\x1eMessageType_DebugLinkFlashDump\x10W\x1a\x04\xa0\xb5\x18\x01\x12\x30\n&MessageType_DebugLinkFlashDumpResponse\x10X\x1a\x04\xa8\xb5\x18\x01\x12\x1f\n\x15MessageType_SoftReset\x10Y\x1a\x04\xa0\xb5\x18\x01\x12\'\n\x1dMessageType_DebugLinkDecision\x10\x64\x1a\x04\xa0\xb5\x18\x01\x12\'\n\x1dMessageType_DebugLinkGetState\x10\x65\x1a\x04\xa0\xb5\x18\x01\x12$\n\x1aMessageType_DebugLinkState\x10\x66\x1a\x04\xa8\xb5\x18\x01\x12#\n\x19MessageType_DebugLinkStop\x10g\x1a\x04\xa0\xb5\x18\x01\x12\"\n\x18MessageType_DebugLinkLog\x10h\x1a\x04\xa8\xb5\x18\x01\x12)\n\x1fMessageType_DebugLinkFillConfig\x10i\x1a\x04\xa8\xb5\x18\x01\x12\"\n\x18MessageType_GetCoinTable\x10j\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_CoinTable\x10k\x1a\x04\x98\xb5\x18\x01\x42,\n\x1a\x63om.keepkey.deviceprotocolB\x0eKeepKeyMessage') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'messages_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'\n\032com.keepkey.deviceprotocolB\016KeepKeyMessage' + _MESSAGETYPE.values_by_name["MessageType_Initialize"]._options = None + _MESSAGETYPE.values_by_name["MessageType_Initialize"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_Ping"]._options = None + _MESSAGETYPE.values_by_name["MessageType_Ping"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_Success"]._options = None + _MESSAGETYPE.values_by_name["MessageType_Success"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_Failure"]._options = None + _MESSAGETYPE.values_by_name["MessageType_Failure"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_ChangePin"]._options = None + _MESSAGETYPE.values_by_name["MessageType_ChangePin"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_WipeDevice"]._options = None + _MESSAGETYPE.values_by_name["MessageType_WipeDevice"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_FirmwareErase"]._options = None + _MESSAGETYPE.values_by_name["MessageType_FirmwareErase"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_FirmwareUpload"]._options = None + _MESSAGETYPE.values_by_name["MessageType_FirmwareUpload"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_GetEntropy"]._options = None + _MESSAGETYPE.values_by_name["MessageType_GetEntropy"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_Entropy"]._options = None + _MESSAGETYPE.values_by_name["MessageType_Entropy"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_GetPublicKey"]._options = None + _MESSAGETYPE.values_by_name["MessageType_GetPublicKey"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_PublicKey"]._options = None + _MESSAGETYPE.values_by_name["MessageType_PublicKey"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_LoadDevice"]._options = None + _MESSAGETYPE.values_by_name["MessageType_LoadDevice"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_ResetDevice"]._options = None + _MESSAGETYPE.values_by_name["MessageType_ResetDevice"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_SignTx"]._options = None + _MESSAGETYPE.values_by_name["MessageType_SignTx"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_Features"]._options = None + _MESSAGETYPE.values_by_name["MessageType_Features"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_PinMatrixRequest"]._options = None + _MESSAGETYPE.values_by_name["MessageType_PinMatrixRequest"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_PinMatrixAck"]._options = None + _MESSAGETYPE.values_by_name["MessageType_PinMatrixAck"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_Cancel"]._options = None + _MESSAGETYPE.values_by_name["MessageType_Cancel"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_TxRequest"]._options = None + _MESSAGETYPE.values_by_name["MessageType_TxRequest"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_TxAck"]._options = None + _MESSAGETYPE.values_by_name["MessageType_TxAck"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_CipherKeyValue"]._options = None + _MESSAGETYPE.values_by_name["MessageType_CipherKeyValue"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_ClearSession"]._options = None + _MESSAGETYPE.values_by_name["MessageType_ClearSession"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_ApplySettings"]._options = None + _MESSAGETYPE.values_by_name["MessageType_ApplySettings"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_ButtonRequest"]._options = None + _MESSAGETYPE.values_by_name["MessageType_ButtonRequest"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_ButtonAck"]._options = None + _MESSAGETYPE.values_by_name["MessageType_ButtonAck"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_GetAddress"]._options = None + _MESSAGETYPE.values_by_name["MessageType_GetAddress"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_Address"]._options = None + _MESSAGETYPE.values_by_name["MessageType_Address"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_EntropyRequest"]._options = None + _MESSAGETYPE.values_by_name["MessageType_EntropyRequest"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_EntropyAck"]._options = None + _MESSAGETYPE.values_by_name["MessageType_EntropyAck"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_SignMessage"]._options = None + _MESSAGETYPE.values_by_name["MessageType_SignMessage"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_VerifyMessage"]._options = None + _MESSAGETYPE.values_by_name["MessageType_VerifyMessage"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_MessageSignature"]._options = None + _MESSAGETYPE.values_by_name["MessageType_MessageSignature"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_PassphraseRequest"]._options = None + _MESSAGETYPE.values_by_name["MessageType_PassphraseRequest"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_PassphraseAck"]._options = None + _MESSAGETYPE.values_by_name["MessageType_PassphraseAck"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_RecoveryDevice"]._options = None + _MESSAGETYPE.values_by_name["MessageType_RecoveryDevice"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_WordRequest"]._options = None + _MESSAGETYPE.values_by_name["MessageType_WordRequest"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_WordAck"]._options = None + _MESSAGETYPE.values_by_name["MessageType_WordAck"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_CipheredKeyValue"]._options = None + _MESSAGETYPE.values_by_name["MessageType_CipheredKeyValue"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_EncryptMessage"]._options = None + _MESSAGETYPE.values_by_name["MessageType_EncryptMessage"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_EncryptedMessage"]._options = None + _MESSAGETYPE.values_by_name["MessageType_EncryptedMessage"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_DecryptMessage"]._options = None + _MESSAGETYPE.values_by_name["MessageType_DecryptMessage"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_DecryptedMessage"]._options = None + _MESSAGETYPE.values_by_name["MessageType_DecryptedMessage"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_SignIdentity"]._options = None + _MESSAGETYPE.values_by_name["MessageType_SignIdentity"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_SignedIdentity"]._options = None + _MESSAGETYPE.values_by_name["MessageType_SignedIdentity"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_GetFeatures"]._options = None + _MESSAGETYPE.values_by_name["MessageType_GetFeatures"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_CharacterRequest"]._options = None + _MESSAGETYPE.values_by_name["MessageType_CharacterRequest"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_CharacterAck"]._options = None + _MESSAGETYPE.values_by_name["MessageType_CharacterAck"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_RawTxAck"]._options = None + _MESSAGETYPE.values_by_name["MessageType_RawTxAck"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_ApplyPolicies"]._options = None + _MESSAGETYPE.values_by_name["MessageType_ApplyPolicies"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_FlashHash"]._options = None + _MESSAGETYPE.values_by_name["MessageType_FlashHash"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_FlashWrite"]._options = None + _MESSAGETYPE.values_by_name["MessageType_FlashWrite"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_FlashHashResponse"]._options = None + _MESSAGETYPE.values_by_name["MessageType_FlashHashResponse"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_DebugLinkFlashDump"]._options = None + _MESSAGETYPE.values_by_name["MessageType_DebugLinkFlashDump"]._serialized_options = b'\240\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_DebugLinkFlashDumpResponse"]._options = None + _MESSAGETYPE.values_by_name["MessageType_DebugLinkFlashDumpResponse"]._serialized_options = b'\250\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_SoftReset"]._options = None + _MESSAGETYPE.values_by_name["MessageType_SoftReset"]._serialized_options = b'\240\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_DebugLinkDecision"]._options = None + _MESSAGETYPE.values_by_name["MessageType_DebugLinkDecision"]._serialized_options = b'\240\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_DebugLinkGetState"]._options = None + _MESSAGETYPE.values_by_name["MessageType_DebugLinkGetState"]._serialized_options = b'\240\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_DebugLinkState"]._options = None + _MESSAGETYPE.values_by_name["MessageType_DebugLinkState"]._serialized_options = b'\250\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_DebugLinkStop"]._options = None + _MESSAGETYPE.values_by_name["MessageType_DebugLinkStop"]._serialized_options = b'\240\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_DebugLinkLog"]._options = None + _MESSAGETYPE.values_by_name["MessageType_DebugLinkLog"]._serialized_options = b'\250\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_DebugLinkFillConfig"]._options = None + _MESSAGETYPE.values_by_name["MessageType_DebugLinkFillConfig"]._serialized_options = b'\250\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_GetCoinTable"]._options = None + _MESSAGETYPE.values_by_name["MessageType_GetCoinTable"]._serialized_options = b'\220\265\030\001' + _MESSAGETYPE.values_by_name["MessageType_CoinTable"]._options = None + _MESSAGETYPE.values_by_name["MessageType_CoinTable"]._serialized_options = b'\230\265\030\001' + _MESSAGETYPE._serialized_start=4978 + _MESSAGETYPE._serialized_end=7290 + _INITIALIZE._serialized_start=31 + _INITIALIZE._serialized_end=43 + _GETFEATURES._serialized_start=45 + _GETFEATURES._serialized_end=58 + _FEATURES._serialized_start=61 + _FEATURES._serialized_end=557 + _GETCOINTABLE._serialized_start=559 + _GETCOINTABLE._serialized_end=601 + _COINTABLE._serialized_start=603 + _COINTABLE._serialized_end=679 + _CLEARSESSION._serialized_start=681 + _CLEARSESSION._serialized_end=695 + _APPLYSETTINGS._serialized_start=697 + _APPLYSETTINGS._serialized_end=818 + _CHANGEPIN._serialized_start=820 + _CHANGEPIN._serialized_end=847 + _PING._serialized_start=849 + _PING._serialized_end=954 + _SUCCESS._serialized_start=956 + _SUCCESS._serialized_end=982 + _FAILURE._serialized_start=984 + _FAILURE._serialized_end=1038 + _BUTTONREQUEST._serialized_start=1040 + _BUTTONREQUEST._serialized_end=1103 + _BUTTONACK._serialized_start=1105 + _BUTTONACK._serialized_end=1116 + _PINMATRIXREQUEST._serialized_start=1118 + _PINMATRIXREQUEST._serialized_end=1173 + _PINMATRIXACK._serialized_start=1175 + _PINMATRIXACK._serialized_end=1202 + _CANCEL._serialized_start=1204 + _CANCEL._serialized_end=1212 + _PASSPHRASEREQUEST._serialized_start=1214 + _PASSPHRASEREQUEST._serialized_end=1233 + _PASSPHRASEACK._serialized_start=1235 + _PASSPHRASEACK._serialized_end=1270 + _GETENTROPY._serialized_start=1272 + _GETENTROPY._serialized_end=1298 + _ENTROPY._serialized_start=1300 + _ENTROPY._serialized_end=1326 + _GETPUBLICKEY._serialized_start=1329 + _GETPUBLICKEY._serialized_end=1491 + _PUBLICKEY._serialized_start=1493 + _PUBLICKEY._serialized_end=1545 + _GETADDRESS._serialized_start=1548 + _GETADDRESS._serialized_end=1727 + _ADDRESS._serialized_start=1729 + _ADDRESS._serialized_end=1755 + _WIPEDEVICE._serialized_start=1757 + _WIPEDEVICE._serialized_end=1769 + _LOADDEVICE._serialized_start=1772 + _LOADDEVICE._serialized_end=1959 + _RESETDEVICE._serialized_start=1962 + _RESETDEVICE._serialized_end=2187 + _ENTROPYREQUEST._serialized_start=2189 + _ENTROPYREQUEST._serialized_end=2205 + _ENTROPYACK._serialized_start=2207 + _ENTROPYACK._serialized_end=2236 + _RECOVERYDEVICE._serialized_start=2239 + _RECOVERYDEVICE._serialized_end=2494 + _WORDREQUEST._serialized_start=2496 + _WORDREQUEST._serialized_end=2509 + _WORDACK._serialized_start=2511 + _WORDACK._serialized_end=2534 + _CHARACTERREQUEST._serialized_start=2536 + _CHARACTERREQUEST._serialized_end=2595 + _CHARACTERACK._serialized_start=2597 + _CHARACTERACK._serialized_end=2660 + _SIGNMESSAGE._serialized_start=2663 + _SIGNMESSAGE._serialized_end=2793 + _VERIFYMESSAGE._serialized_start=2795 + _VERIFYMESSAGE._serialized_end=2891 + _MESSAGESIGNATURE._serialized_start=2893 + _MESSAGESIGNATURE._serialized_end=2947 + _ENCRYPTMESSAGE._serialized_start=2949 + _ENCRYPTMESSAGE._serialized_end=3067 + _ENCRYPTEDMESSAGE._serialized_start=3069 + _ENCRYPTEDMESSAGE._serialized_end=3133 + _DECRYPTMESSAGE._serialized_start=3135 + _DECRYPTMESSAGE._serialized_end=3216 + _DECRYPTEDMESSAGE._serialized_start=3218 + _DECRYPTEDMESSAGE._serialized_end=3270 + _CIPHERKEYVALUE._serialized_start=3273 + _CIPHERKEYVALUE._serialized_end=3413 + _CIPHEREDKEYVALUE._serialized_start=3415 + _CIPHEREDKEYVALUE._serialized_end=3448 + _SIGNTX._serialized_start=3451 + _SIGNTX._serialized_end=3657 + _TXREQUEST._serialized_start=3660 + _TXREQUEST._serialized_end=3793 + _TXACK._serialized_start=3795 + _TXACK._serialized_end=3832 + _RAWTXACK._serialized_start=3834 + _RAWTXACK._serialized_end=3877 + _SIGNIDENTITY._serialized_start=3879 + _SIGNIDENTITY._serialized_end=4004 + _SIGNEDIDENTITY._serialized_start=4006 + _SIGNEDIDENTITY._serialized_end=4078 + _APPLYPOLICIES._serialized_start=4080 + _APPLYPOLICIES._serialized_end=4124 + _FLASHHASH._serialized_start=4126 + _FLASHHASH._serialized_end=4189 + _FLASHWRITE._serialized_start=4191 + _FLASHWRITE._serialized_end=4249 + _FLASHHASHRESPONSE._serialized_start=4251 + _FLASHHASHRESPONSE._serialized_end=4284 + _DEBUGLINKFLASHDUMP._serialized_start=4286 + _DEBUGLINKFLASHDUMP._serialized_end=4339 + _DEBUGLINKFLASHDUMPRESPONSE._serialized_start=4341 + _DEBUGLINKFLASHDUMPRESPONSE._serialized_end=4383 + _SOFTRESET._serialized_start=4385 + _SOFTRESET._serialized_end=4396 + _FIRMWAREERASE._serialized_start=4398 + _FIRMWAREERASE._serialized_end=4413 + _FIRMWAREUPLOAD._serialized_start=4415 + _FIRMWAREUPLOAD._serialized_end=4470 + _DEBUGLINKDECISION._serialized_start=4472 + _DEBUGLINKDECISION._serialized_end=4507 + _DEBUGLINKGETSTATE._serialized_start=4509 + _DEBUGLINKGETSTATE._serialized_end=4528 + _DEBUGLINKSTATE._serialized_start=4531 + _DEBUGLINKSTATE._serialized_end=4874 + _DEBUGLINKSTOP._serialized_start=4876 + _DEBUGLINKSTOP._serialized_end=4891 + _DEBUGLINKLOG._serialized_start=4893 + _DEBUGLINKLOG._serialized_end=4952 + _DEBUGLINKFILLCONFIG._serialized_start=4954 + _DEBUGLINKFILLCONFIG._serialized_end=4975 +# @@protoc_insertion_point(module_scope) diff --git a/electrum/plugins/keepkey/keepkeylib/protobuf_json.py b/electrum/plugins/keepkey/keepkeylib/protobuf_json.py new file mode 100644 index 000000000000..908b9065b8e9 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/protobuf_json.py @@ -0,0 +1,142 @@ +# JSON serialization support for Google's protobuf Messages +# Copyright (c) 2009, Paul Dovbush +# All rights reserved. +# http://code.google.com/p/protobuf-json/ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' +Provide serialization and de-serialization of Google's protobuf Messages into/from JSON format. +''' + +# groups are deprecated and not supported; +# Note that preservation of unknown fields is currently not available for Python (c) google docs +# extensions is not supported from 0.0.5 (due to gpb2.3 changes) + +__version__='0.0.5' +__author__='Paul Dovbush ' + + +import json +from google.protobuf.descriptor import FieldDescriptor as FD +import binascii +import types_pb2 as types + +class ParseError(Exception): pass + + +def json2pb(pb, js): + ''' convert JSON string to google.protobuf.descriptor instance ''' + for field in pb.DESCRIPTOR.fields: + if field.name not in js: + continue + if field.type == FD.TYPE_MESSAGE: + pass + elif field.type in _js2ftype: + ftype = _js2ftype[field.type] + else: + raise ParseError("Field %s.%s of type '%d' is not supported" % (pb.__class__.__name__, field.name, field.type, )) + value = js[field.name] + if field.label == FD.LABEL_REPEATED: + pb_value = getattr(pb, field.name, None) + for v in value: + if field.type == FD.TYPE_MESSAGE: + json2pb(pb_value.add(), v) + else: + pb_value.append(ftype(v)) + else: + if field.type == FD.TYPE_MESSAGE: + json2pb(getattr(pb, field.name, None), value) + else: + setattr(pb, field.name, ftype(value)) + return pb + + + +def pb2json(pb): + ''' convert google.protobuf.descriptor instance to JSON string ''' + js = {} + # fields = pb.DESCRIPTOR.fields #all fields + fields = pb.ListFields() #only filled (including extensions) + for field,value in fields: + if field.type == FD.TYPE_MESSAGE: + ftype = pb2json + elif field.type == FD.TYPE_ENUM: + ftype = lambda x: field.enum_type.values[x].name + elif field.type in _ftype2js: + ftype = _ftype2js[field.type] + else: + raise ParseError("Field %s.%s of type '%d' is not supported" % (pb.__class__.__name__, field.name, field.type, )) + if field.label == FD.LABEL_REPEATED: + js_value = [] + for v in value: + js_value.append(ftype(v)) + else: + js_value = ftype(value) + js[field.name] = js_value + return js + + +_ftype2js = { + FD.TYPE_DOUBLE: float, + FD.TYPE_FLOAT: float, + FD.TYPE_INT64: long, + FD.TYPE_UINT64: long, + FD.TYPE_INT32: int, + FD.TYPE_FIXED64: float, + FD.TYPE_FIXED32: float, + FD.TYPE_BOOL: bool, + FD.TYPE_STRING: unicode, + #FD.TYPE_MESSAGE handled specially + FD.TYPE_BYTES: lambda x: binascii.hexlify(x), + FD.TYPE_UINT32: int, + # FD.TYPE_ENUM: handled specially + FD.TYPE_SFIXED32: float, + FD.TYPE_SFIXED64: float, + FD.TYPE_SINT32: int, + FD.TYPE_SINT64: long, +} + +_js2ftype = { + FD.TYPE_DOUBLE: float, + FD.TYPE_FLOAT: float, + FD.TYPE_INT64: long, + FD.TYPE_UINT64: long, + FD.TYPE_INT32: int, + FD.TYPE_FIXED64: float, + FD.TYPE_FIXED32: float, + FD.TYPE_BOOL: bool, + FD.TYPE_STRING: unicode, + # FD.TYPE_MESSAGE handled specially + FD.TYPE_BYTES: lambda x: binascii.unhexlify(x), + FD.TYPE_UINT32: int, + FD.TYPE_ENUM: lambda x: getattr(types, x), + FD.TYPE_SFIXED32: float, + FD.TYPE_SFIXED64: float, + FD.TYPE_SINT32: int, + FD.TYPE_SINT64: long, +} diff --git a/electrum/plugins/keepkey/keepkeylib/qt/__init__.py b/electrum/plugins/keepkey/keepkeylib/qt/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/electrum/plugins/keepkey/keepkeylib/qt/pinmatrix.py b/electrum/plugins/keepkey/keepkeylib/qt/pinmatrix.py new file mode 100644 index 000000000000..4ab8e93aeaed --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/qt/pinmatrix.py @@ -0,0 +1,136 @@ +from __future__ import print_function +import sys +import math + +try: + from PyQt4.QtGui import (QPushButton, QLineEdit, QSizePolicy, QRegExpValidator, QLabel, + QApplication, QWidget, QGridLayout, QVBoxLayout, QHBoxLayout) + from PyQt4.QtCore import QObject, SIGNAL, QRegExp, Qt, QT_VERSION_STR +except: + from PyQt5.QtWidgets import (QPushButton, QLineEdit, QSizePolicy, QLabel, + QApplication, QWidget, QGridLayout, QVBoxLayout, QHBoxLayout) + from PyQt5.QtGui import QRegExpValidator + from PyQt5.QtCore import QRegExp, Qt, QT_VERSION_STR + + +class PinButton(QPushButton): + def __init__(self, password, encoded_value): + super(PinButton, self).__init__('?') + self.password = password + self.encoded_value = encoded_value + + if QT_VERSION_STR >= '5': + self.clicked.connect(self._pressed) + elif QT_VERSION_STR >= '4': + QObject.connect(self, SIGNAL('clicked()'), self._pressed) + else: + raise Exception('Unsupported Qt version') + + def _pressed(self): + self.password.setText(self.password.text() + str(self.encoded_value)) + self.password.setFocus() + + +class PinMatrixWidget(QWidget): + ''' + Displays widget with nine blank buttons and password box. + Encodes button clicks into sequence of numbers for passing + into PinAck messages of KeepKey. + + show_strength=True may be useful for entering new PIN + ''' + def __init__(self, show_strength=True, parent=None): + super(PinMatrixWidget, self).__init__(parent) + + self.password = QLineEdit() + self.password.setValidator(QRegExpValidator(QRegExp('[1-9]+'), None)) + self.password.setEchoMode(QLineEdit.Password) + + if QT_VERSION_STR >= '5': + self.password.textChanged.connect(self._password_changed) + elif QT_VERSION_STR >= '4': + QObject.connect(self.password, SIGNAL('textChanged(QString)'), self._password_changed) + else: + raise Exception('Unsupported Qt version') + + self.strength = QLabel() + self.strength.setMinimumWidth(75) + self.strength.setAlignment(Qt.AlignCenter) + self._set_strength(0) + + grid = QGridLayout() + grid.setSpacing(0) + for y in range(3)[::-1]: + for x in range(3): + button = PinButton(self.password, x + y * 3 + 1) + button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + button.setFocusPolicy(Qt.NoFocus) + grid.addWidget(button, 3 - y, x) + + hbox = QHBoxLayout() + hbox.addWidget(self.password) + if show_strength: + hbox.addWidget(self.strength) + + vbox = QVBoxLayout() + vbox.addLayout(grid) + vbox.addLayout(hbox) + self.setLayout(vbox) + + def _set_strength(self, strength): + if strength < 3000: + self.strength.setText('weak') + self.strength.setStyleSheet("QLabel { color : #d00; }") + elif strength < 60000: + self.strength.setText('fine') + self.strength.setStyleSheet("QLabel { color : #db0; }") + elif strength < 360000: + self.strength.setText('strong') + self.strength.setStyleSheet("QLabel { color : #0a0; }") + else: + self.strength.setText('ULTIMATE') + self.strength.setStyleSheet("QLabel { color : #000; font-weight: bold;}") + + def _password_changed(self, password): + self._set_strength(self.get_strength()) + + def get_strength(self): + digits = len(set(str(self.password.text()))) + strength = math.factorial(9) / math.factorial(9 - digits) + return strength + + def get_value(self): + return self.password.text() + + +if __name__ == '__main__': + ''' + Demo application showing PinMatrix widget in action + ''' + a = QApplication(sys.argv) + + matrix = PinMatrixWidget() + + def clicked(): + print("PinMatrix value is", matrix.get_value()) + print("Possible button combinations:", matrix.get_strength()) + sys.exit() + + ok = QPushButton('OK') + if QT_VERSION_STR >= '5': + ok.clicked.connect(clicked) + elif QT_VERSION_STR >= '4': + QObject.connect(ok, SIGNAL('clicked()'), clicked) + else: + raise Exception('Unsupported Qt version') + + vbox = QVBoxLayout() + vbox.addWidget(matrix) + vbox.addWidget(ok) + + w = QWidget() + w.setLayout(vbox) + w.move(100, 100) + w.show() + + a.exec_() diff --git a/electrum/plugins/keepkey/keepkeylib/tools.py b/electrum/plugins/keepkey/keepkeylib/tools.py new file mode 100644 index 000000000000..cf2f99c881d5 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/tools.py @@ -0,0 +1,172 @@ +import hashlib +import binascii +import struct +import sys + +Hash = lambda x: hashlib.sha256(hashlib.sha256(x).digest()).digest() + +HARDENED_FLAG = 1 << 31 + + +def H_(x): + """ + Shortcut function that "hardens" a number in a BIP44 path. + """ + return x | HARDENED_FLAG + + +def btc_hash(data): + """ + Double-SHA256 hash as used in BTC + """ + return hashlib.sha256(hashlib.sha256(data).digest()).digest() + +def hash_160(public_key): + md = hashlib.new('ripemd160') + md.update(hashlib.sha256(public_key).digest()) + return md.digest() + + +def hash_160_to_bc_address(h160, address_type): + vh160 = chr(address_type) + h160 + h = Hash(vh160) + addr = vh160 + h[0:4] + return b58encode(addr) + +def compress_pubkey(public_key): + if public_key[0] == '\x04': + return chr((ord(public_key[64]) & 1) + 2) + public_key[1:33] + raise Exception("Pubkey is already compressed") + +def public_key_to_bc_address(public_key, address_type, compress=True): + if public_key[0] == '\x04' and compress: + public_key = compress_pubkey(public_key) + + h160 = hash_160(public_key) + return hash_160_to_bc_address(h160, address_type) + +__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +__b58base = len(__b58chars) + +if sys.version_info < (3,): + def iterbytes(data): + return (ord (char) for char in data) + +else: + iterbytes = lambda x: iter(x) + +def b58encode(v): + """ encode v, which is a string of bytes, to base58.""" + + long_value = 0 + for c in iterbytes(v): + long_value = long_value * 256 + c + + result = '' + while long_value >= __b58base: + div, mod = divmod(long_value, __b58base) + result = __b58chars[mod] + result + long_value = div + result = __b58chars[long_value] + result + + # Bitcoin does a little leading-zero-compression: + # leading 0-bytes in the input become leading-1s + nPad = 0 + for c in iterbytes(v): + if c == 0: + nPad += 1 + else: + break + + return (__b58chars[0] * nPad) + result + + +def b58decode(v, length): + """ decode v into a string of len bytes.""" + long_value = 0 + for (i, c) in enumerate(v[::-1]): + long_value += __b58chars.find(c) * (__b58base ** i) + + result = b'' + while long_value >= 256: + div, mod = divmod(long_value, 256) + result = struct.pack('B', mod) + result + long_value = div + result = struct.pack('B', long_value) + result + + nPad = 0 + for c in v: + if c == __b58chars[0]: + nPad += 1 + else: + break + + result = b'\x00' * nPad + result + if length is not None and len(result) != length: + return None + + return result + + +def parse_path(nstr): + """ + Convert BIP32 path string to list of uint32 integers with hardened flags. + Several conventions are supported to set the hardened flag: -1, 1', 1h + e.g.: "0/1h/1" -> [0, 0x80000001, 1] + :param nstr: path string + :return: list of integers + """ + if not nstr: + return [] + + n = nstr.split('/') + + # m/a/b/c => a/b/c + if n[0] == 'm': + n = n[1:] + + # coin_name/a/b/c => 44'/SLIP44_constant'/a/b/c + #if n[0] in slip44: + # coin_id = slip44[n[0]] + # n[0:1] = ['44h', '{}h'.format(coin_id)] + + def str_to_harden(x): + if x.startswith('-'): + return H_(abs(int(x))) + elif x.endswith(('h', "'")): + return H_(int(x[:-1])) + else: + return int(x) + + try: + return list(str_to_harden(x) for x in n) + except Exception: + raise ValueError('Invalid BIP32 path', nstr) + + +def monkeypatch_google_protobuf_text_format(): + # monkeypatching: text formatting of protobuf messages + import google.protobuf.text_format + import google.protobuf.descriptor + + _oldPrintFieldValue = google.protobuf.text_format.PrintFieldValue + + def _customPrintFieldValue(field, value, out, indent=0, as_utf8=False, as_one_line=False, pointy_brackets=False, float_format=None): + if field.type == google.protobuf.descriptor.FieldDescriptor.TYPE_BYTES: + _oldPrintFieldValue(field, 'hex(%s)' % binascii.hexlify(value), out, indent, as_utf8, as_one_line) + else: + _oldPrintFieldValue(field, value, out, indent, as_utf8, as_one_line) + + google.protobuf.text_format.PrintFieldValue = _customPrintFieldValue + + +def int_to_big_endian(value): + import struct + + res = b'' + while 0 < value: + res = struct.pack("B", value & 0xff) + res + value = value >> 8 + + return res + diff --git a/electrum/plugins/keepkey/keepkeylib/transport.py b/electrum/plugins/keepkey/keepkeylib/transport.py new file mode 100644 index 000000000000..9ff25dfaf351 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/transport.py @@ -0,0 +1,132 @@ +import struct +from . import mapping + +class NotImplementedException(Exception): + pass + +class ConnectionError(Exception): + pass + +class Transport(object): + def __init__(self, device, *args, **kwargs): + self.device = device + self.session_depth = 0 + self._open() + + def _open(self): + raise NotImplementedException("Not implemented") + + def _close(self): + raise NotImplementedException("Not implemented") + + def _write(self, msg, protobuf_msg): + raise NotImplementedException("Not implemented") + + def _read(self): + raise NotImplementedException("Not implemented") + + def _session_begin(self): + pass + + def _session_end(self): + pass + + def ready_to_read(self): + """ + Returns True if there is data to be read from the transport. Otherwise, False. + """ + raise NotImplementedException("Not implemented") + + def session_begin(self): + """ + Apply a lock to the device in order to preform synchronous multistep "conversations" with the device. For example, before entering the transaction signing workflow, one begins a session. After the transaction is complete, the session may be ended. + """ + if self.session_depth == 0: + self._session_begin() + self.session_depth += 1 + + def session_end(self): + """ + End a session. Se session_begin for an in depth description of TREZOR sessions. + """ + self.session_depth -= 1 + self.session_depth = max(0, self.session_depth) + if self.session_depth == 0: + self._session_end() + + def close(self): + """ + Close the connection to the physical device or file descriptor represented by the Transport. + """ + self._close() + + def write(self, msg): + """ + Write mesage to tansport. msg should be a member of a valid `protobuf class `_ with a SerializeToString() method. + """ + ser = msg.SerializeToString() + header = struct.pack(">HL", mapping.get_type(msg), len(ser)) + self._write(b"##" + header + ser, msg) + + def read(self): + """ + If there is data available to be read from the transport, reads the data and tries to parse it as a protobuf message. If the parsing succeeds, return a protobuf object. + Otherwise, returns None. + """ + if not self.ready_to_read(): + return None + + data = self._read() + if data is None: + return None + + return self._parse_message(data) + + def read_blocking(self): + """ + Same as read, except blocks untill data is available to be read. + """ + while True: + data = self._read() + if data != None: + break + + return self._parse_message(data) + + def _parse_message(self, data): + (msg_type, data) = data + if msg_type == 'protobuf': + return data + else: + inst = mapping.get_class(msg_type)() + inst.ParseFromString(data) + return inst + + def _read_headers(self, read_f): + # Try to read headers until some sane value are detected + is_ok = False + while not is_ok: + + # Align cursor to the beginning of the header ("##") + c = read_f.read(1) + i = 0 + while c != b"#": + i += 1 + if i >= 64: + # timeout + raise Exception("Timed out while waiting for the magic character") + c = read_f.read(1) + + if read_f.read(1) != b"#": + # Second character must be # to be valid header + raise Exception("Second magic character is broken") + + # Now we're most likely on the beginning of the header + try: + headerlen = struct.calcsize(">HL") + (msg_type, datalen) = struct.unpack(">HL", read_f.read(headerlen)) + break + except: + raise Exception("Cannot parse header length") + + return (msg_type, datalen) diff --git a/electrum/plugins/keepkey/keepkeylib/transport_fake.py b/electrum/plugins/keepkey/keepkeylib/transport_fake.py new file mode 100644 index 000000000000..d50bec7aaaf5 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/transport_fake.py @@ -0,0 +1,24 @@ +'''FakeTransport implements dummy interface for Transport.''' + +# Local serial port loopback: socat PTY,link=COM8 PTY,link=COM9 + +from .transport import Transport, NotImplementedException + +class FakeTransport(Transport): + def __init__(self, device, *args, **kwargs): + super(FakeTransport, self).__init__(device, *args, **kwargs) + + def _open(self): + pass + + def _close(self): + pass + + def ready_to_read(self): + return False + + def _write(self, msg, protobuf_msg): + pass + + def _read(self): + raise NotImplementedException("Not implemented") diff --git a/electrum/plugins/keepkey/keepkeylib/transport_hid.py b/electrum/plugins/keepkey/keepkeylib/transport_hid.py new file mode 100644 index 000000000000..44eb7e436d0e --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/transport_hid.py @@ -0,0 +1,199 @@ +'''USB HID implementation of Transport.''' +import math +from hashlib import sha256 +import time, json, base64, struct +from .transport import Transport, ConnectionError +import binascii +import platform + +import hid + +DEVICE_IDS = [ + (0x2B24, 0x0001), # KeepKey +] + + +MAX_MSG_SIZE = 59 + +INTERFACE_MAPPING = { + "normal_usb": 0, + "debug_link": 1, + } + +class FakeRead(object): + # Let's pretend we have a file-like interface + def __init__(self, func): + self.func = func + + def read(self, size): + return self.func(size) + +def is_normal_link(device): + if device['usage_page'] == 0xff00: + return True + + if device['interface_number'] == 0: + return True + + # MacOS reports -1 as the interface_number for everything, + # inspect based on the path instead. + if platform.system() == 'Darwin': + if device['interface_number'] == -1: + return device['path'].endswith(b'0') + + return False + +def is_debug_link(device): + if device['usage_page'] == 0xff01: + return True + + if device['interface_number'] == 1: + return True + + # MacOS reports -1 as the interface_number for everything, + # inspect based on the path instead. + if platform.system() == 'Darwin': + if device['interface_number'] == -1: + return device['path'].endswith(b'1') + + return False + +class HidTransport(Transport): + def __init__(self, device_paths, *args, **kwargs): + self.hid = None + self.buffer = '' + #select the appropriate transport + self.use_debug_link = kwargs.get("debug_link", False) + self.interface_index = 0 + if self.use_debug_link: self.interface_index += 1 + #stale device paths are a problem here unless we re-enumerate + device_paths = self.enumerate()[0] + self.path = device_paths[self.interface_index] + super(HidTransport, self).__init__(self.path, *args, **kwargs) + + @classmethod + def enumerate(cls): + """ + Return a list of available KeepKey devices. + """ + devices = {} + for d in hid.enumerate(0, 0): + vendor_id = d['vendor_id'] + product_id = d['product_id'] + serial_number = d['serial_number'] + interface_number = d['interface_number'] + path = d['path'] + + # HIDAPI on Mac cannot detect correct HID interfaces, so device with + # DebugLink doesn't work on Mac... + if devices.get(serial_number) != None and devices[serial_number][0] == path: + raise Exception("Two devices with the same path and S/N found. This is Mac, right? :-/") + + if (vendor_id, product_id) in DEVICE_IDS: + devices.setdefault(serial_number, [None, None, None]) + if is_normal_link(d): + devices[serial_number][0] = path + elif is_debug_link(d): + devices[serial_number][1] = path + else: + raise Exception("Unknown USB interface number: %d" % interface_number) + + # List of two-tuples (path_normal, path_debuglink) + return list(devices.values()) + + def is_connected(self): + """ + Check if the device is still connected. + """ + for d in hid.enumerate(0, 0): + if d['path'] == self.device: + return True + return False + + def _open(self): + self.apdus = [] + self.buffer = bytearray() + self.hid = hid.device() + self.hid.open_path(self.device) + self.hid.set_nonblocking(True) + # the following was needed just for TREZOR Shield + # self.hid.send_feature_report([0x41, 0x01]) # enable UART + # self.hid.send_feature_report([0x43, 0x03]) # purge TX/RX FIFOs + + def _close(self): + self.hid.close() + self.buffer = bytearray() + self.hid = None + self.apdu = None + + def ready_to_read(self): + return False + + def _msg_to_apdus(self, msg): + #generate app/client data + app_id = 'https://www.keepkey.com' + window_location = 'navigator.id.getAssertion' + challenge = 'KPKYKPKYKPKYKPKYKPKYKPKYKPKYKPKY' + client_data = '{{"typ": "{}", "challenge": "{}", "origin": "{}"}}'.format(window_location, challenge, app_id) + app_param = sha256(app_id.encode('utf8')).digest() + client_param = sha256(client_data.encode('utf8')).digest() + total_frames = math.ceil(len(msg)/float(MAX_MSG_SIZE)) + frame_i = 0 + chunks = [] + while len(msg): + flags = 0 + flags = flags | (0x40 if self.use_debug_link else 0) + chunks.append(struct.pack(" 63: + # Command report + raise Exception("Not implemented") + + # Payload received, skip the report ID + self.buffer.extend(bytearray(data[1:])) + + ret = self.buffer[:length] + self.buffer = self.buffer[length:] + return bytes(ret) diff --git a/electrum/plugins/keepkey/keepkeylib/transport_pipe.py b/electrum/plugins/keepkey/keepkeylib/transport_pipe.py new file mode 100644 index 000000000000..5faa219c1eb7 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/transport_pipe.py @@ -0,0 +1,60 @@ +from __future__ import print_function +import os +from select import select +from .transport import Transport + +"""PipeTransport implements fake wire transport over local named pipe. +Use this transport for talking with trezor simulator.""" + +class PipeTransport(Transport): + def __init__(self, device, is_device, *args, **kwargs): + self.is_device = is_device # Set True if act as device + + super(PipeTransport, self).__init__(device, *args, **kwargs) + + def _open(self): + if self.is_device: + self.filename_read = self.device+'.to' + self.filename_write = self.device+'.from' + + os.mkfifo(self.filename_read, 0o600) + os.mkfifo(self.filename_write, 0o600) + else: + self.filename_read = self.device+'.from' + self.filename_write = self.device+'.to' + + if not os.path.exists(self.filename_write): + raise Exception("Not connected") + + self.write_fd = os.open(self.filename_write, os.O_RDWR)#|os.O_NONBLOCK) + self.write_f = os.fdopen(self.write_fd, 'w+b', 0) + + self.read_fd = os.open(self.filename_read, os.O_RDWR)#|os.O_NONBLOCK) + self.read_f = os.fdopen(self.read_fd, 'rb', 0) + + def _close(self): + self.read_f.close() + self.write_f.close() + if self.is_device: + os.unlink(self.filename_read) + os.unlink(self.filename_write) + + def ready_to_read(self): + rlist, _, _ = select([self.read_f], [], [], 0) + return len(rlist) > 0 + + def _write(self, msg, protobuf_msg): + try: + self.write_f.write(msg) + self.write_f.flush() + except OSError: + print("Error while writing to socket") + raise + + def _read(self): + try: + (msg_type, datalen) = self._read_headers(self.read_f) + return (msg_type, self.read_f.read(datalen)) + except IOError: + print("Failed to read from device") + raise diff --git a/electrum/plugins/keepkey/keepkeylib/transport_serial.py b/electrum/plugins/keepkey/keepkeylib/transport_serial.py new file mode 100644 index 000000000000..de62791201dd --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/transport_serial.py @@ -0,0 +1,42 @@ +from __future__ import print_function + +'''SerialTransport implements wire transport over serial port.''' + +# Local serial port loopback: socat PTY,link=COM8 PTY,link=COM9 + +from select import select +import serial +from select import select +from .transport import Transport + +class SerialTransport(Transport): + def __init__(self, device, *args, **kwargs): + self.serial = None + super(SerialTransport, self).__init__(device, *args, **kwargs) + + def _open(self): + self.serial = serial.Serial(self.device, 115200, timeout=10, writeTimeout=10) + + def _close(self): + self.serial.close() + self.serial = None + + def ready_to_read(self): + rlist, _, _ = select([self.serial], [], [], 0) + return len(rlist) > 0 + + def _write(self, msg, protobuf_msg): + try: + self.serial.write(msg) + self.serial.flush() + except serial.SerialException: + print("Error while writing to socket") + raise + + def _read(self): + try: + (msg_type, datalen) = self._read_headers(self.serial) + return (msg_type, self.serial.read(datalen)) + except serial.SerialException: + print("Failed to read from device") + raise diff --git a/electrum/plugins/keepkey/keepkeylib/transport_socket.py b/electrum/plugins/keepkey/keepkeylib/transport_socket.py new file mode 100644 index 000000000000..49652b28d0f7 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/transport_socket.py @@ -0,0 +1,112 @@ +from __future__ import print_function + +'''SocketTransport implements TCP socket interface for Transport.''' + +import socket +from select import select +from .transport import Transport + +class SocketTransportClient(Transport): + def __init__(self, device, *args, **kwargs): + device = device.split(':') + if len(device) < 2: + device = ('0.0.0.0', int(device[0])) + else: + device = (device[0], int(device[1])) + + self.socket = None + super(SocketTransportClient, self).__init__(device, *args, **kwargs) + + def _open(self): + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect(self.device) + self.filelike = self.socket.makefile() + + def _close(self): + self.socket.close() + self.socket = None + self.filelike = None + + def ready_to_read(self): + rlist, _, _ = select([self.socket], [], [], 0) + return len(rlist) > 0 + + def _write(self, msg, protobuf_msg): + self.socket.sendall(msg) + + def _read(self): + try: + (msg_type, datalen) = self._read_headers(self.filelike) + return (msg_type, self.filelike.read(datalen)) + except socket.error: + print("Failed to read from device") + return None + +class SocketTransport(Transport): + def __init__(self, device, *args, **kwargs): + device = device.split(':') + if len(device) < 2: + device = ('0.0.0.0', int(device[0])) + else: + device = (device[0], int(device[1])) + + self.socket = None + self.client = None + self.filelike = None + + super(SocketTransport, self).__init__(device, *args, **kwargs) + + def _open(self): + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + #self.socket.setblocking(0) + + self.socket.bind(self.device) + self.socket.listen(5) + + def _disconnect_client(self): + print("Disconnecting client") + if self.client != None: + self.client.close() + self.client = None + self.filelike = None + + def _close(self): + self._disconnect_client() + self.socket.close() + self.socket = None + + def ready_to_read(self): + if self.filelike: + # Connected + rlist, _, _ = select([self.client], [], [], 0) + return len(rlist) > 0 + else: + # Waiting for connection + rlist, _, _ = select([self.socket], [], [], 0) + if len(rlist) > 0: + (self.client, ipaddr) = self.socket.accept() + print("Connected", ipaddr[0]) + self.filelike = self.client.makefile() + return self.ready_to_read() + return False + + def _write(self, msg, protobuf_msg): + if self.filelike: + # None on disconnected client + + try: + self.filelike.write(msg) + self.filelike.flush() + except socket.error: + print("Socket error") + self._disconnect_client() + + def _read(self): + try: + (msg_type, datalen) = self._read_headers(self.filelike) + return (msg_type, self.filelike.read(datalen)) + except Exception: + print("Failed to read from device") + self._disconnect_client() + return None diff --git a/electrum/plugins/keepkey/keepkeylib/transport_udp.py b/electrum/plugins/keepkey/keepkeylib/transport_udp.py new file mode 100644 index 000000000000..05767de72f5d --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/transport_udp.py @@ -0,0 +1,65 @@ +from __future__ import print_function + +'''SocketTransport implements TCP socket interface for Transport.''' + +import socket +from select import select +from .transport import Transport + +class FakeRead(object): + # Let's pretend we have a file-like interface + def __init__(self, func): + self.func = func + + def read(self, size): + return self.func(size) + + +class UDPTransport(Transport): + def __init__(self, device, *args, **kwargs): + self.buffer = b'' + device = device.split(':') + if len(device) < 2: + device = ('0.0.0.0', int(device[0])) + else: + device = (device[0], int(device[1])) + + self.socket = None + + super(UDPTransport, self).__init__(device, *args, **kwargs) + + def _open(self): + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.connect(self.device) + + def _close(self): + self.socket.close() + self.socket = None + self.buffer = '' + + def ready_to_read(self): + rlist, _, _ = select([self.socket], [], [], 0) + return len(rlist) > 0 + + def _write(self, msg, protobuf_msg): + + for chunk in [msg[i:i+63] for i in range(0, len(msg), 63)]: + chunk = chunk + b'\0' * (63 - len(chunk)) + self.socket.send(b'?' + chunk) + + def _read(self): + try: + (msg_type, datalen) = self._read_headers(FakeRead(self._raw_read)) + return (msg_type, self._raw_read(datalen)) + except socket.error: + print("Failed to read from device") + return None + + def _raw_read(self, length): + while len(self.buffer) < length: + data = self.socket.recv(64) + self.buffer += data[1:] + + ret = self.buffer[:length] + self.buffer = self.buffer[length:] + return ret diff --git a/electrum/plugins/keepkey/keepkeylib/transport_webusb.py b/electrum/plugins/keepkey/keepkeylib/transport_webusb.py new file mode 100644 index 000000000000..ee1a650ce4e8 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/transport_webusb.py @@ -0,0 +1,141 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2018 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library 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 Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +import importlib +import logging +import sys +import time +import atexit + +from .transport import Transport, ConnectionError + +import usb1 + +_libusb_version = usb1.getVersion() +_libusb_version = (_libusb_version.major, _libusb_version.minor, _libusb_version.micro) + +class FakeRead(object): + # Let's pretend we have a file-like interface + def __init__(self, func): + self.func = func + + def read(self, size): + return self.func(size) + +DEVICE_IDS = [ + (0x2B24, 0x0002), # KeepKey +] + +class WebUsbTransport(Transport): + """ + WebUsbTransport implements transport over WebUSB interface. + """ + + context = None + + def __init__(self, device, *args, **kwargs): + self.buffer = bytearray() + + if kwargs.get("debug_link", False): + self.interface = 1 + self.endpoint = 2 + else: + self.interface = 0 + self.endpoint = 1 + + self.device = device + self.handle = None + + super(WebUsbTransport, self).__init__(device, *args, **kwargs) + + def _open(self): + self.handle = self.device.open() + if self.handle is None: + if sys.platform.startswith("linux"): + args = (UDEV_RULES_STR,) + else: + args = () + raise IOError("Cannot open device", *args) + + self.handle.claimInterface(self.interface) + + def _close(self): + if self.handle is not None: + self.handle.releaseInterface(self.interface) + self.handle.close() + self.handle = None + + @classmethod + def enumerate(cls): + if not cls.context: + cls.context = usb1.USBContext() + cls.context.open() + atexit.register(cls.context.close) + + devices = [] + for dev in cls.context.getDeviceIterator(skip_on_error=True): + + usb_id = (dev.getVendorID(), dev.getProductID()) + if usb_id not in DEVICE_IDS: + continue + try: + # Workaround for libusb < 1.0.22 on windows + if sys.platform == 'win32' and _libusb_version < (1, 0, 22): + # this windows workaround pulled from github.com/trezor/python-trezor + # workaround for issue #223: + # on certain combinations of Windows USB drivers and libusb versions, + # Trezor is returned twice (possibly because Windows know it as both + # a HID and a WebUSB device), and one of the returned devices is + # non-functional. + dev.getProduct() + devices.append(dev) + except usb1.USBErrorNotSupported: + pass + + return devices + + def _write(self, msg, protobuf_msg): + + msg = bytearray(msg) + while len(msg): + # add reportID and padd with zeroes if necessary + self.handle.interruptWrite(self.endpoint, [63, ] + list(msg[:63]) + [0] * (63 - len(msg[:63]))) + msg = msg[63:] + + def _read(self): + (msg_type, datalen) = self._read_headers(FakeRead(self._raw_read)) + return (msg_type, self._raw_read(datalen)) + + def _raw_read(self, length): + start = time.time() + endpoint = 0x80 | self.endpoint + while len(self.buffer) < length: + while True: + data = self.handle.interruptRead(endpoint, 64) + if data: + break + else: + time.sleep(0.001) + + if len(data) != 64: + raise TransportException("Unexpected chunk size: %d" % len(chunk)) + + self.buffer.extend(bytearray(data[1:])) + + ret = self.buffer[:length] + self.buffer = self.buffer[length:] + return bytes(ret) + diff --git a/electrum/plugins/keepkey/keepkeylib/tx_api.py b/electrum/plugins/keepkey/keepkeylib/tx_api.py new file mode 100644 index 000000000000..7a9d556a270f --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/tx_api.py @@ -0,0 +1,153 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2012-2016 Marek Palatinus +# Copyright (C) 2012-2016 Pavol Rusnak +# Copyright (C) 2016 Jochen Hoenicke +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This library 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +import binascii +from decimal import Decimal +import requests +import json +import struct + +from . import types_pb2 as proto_types + +cache_dir = None + + +def pack_varint(n): + if n < 253: + return struct.pack("= 253: + # we assume cnt < 253, so we can treat varIntLen(cnt) as 1 + raise ValueError('Too many joinsplits') + extra_data_len = 1 + joinsplit_cnt * 1802 + 32 + 64 + raw = self.fetch_json(self.url, 'rawtx', txhash) + raw = binascii.unhexlify(raw['rawtx']) + t.extra_data = raw[-extra_data_len:] + + if "_dash" in self.network: + dip2_type = data.get("type", 0) + + if t.version == 3 and dip2_type != 0: + # It's a DIP2 special TX with payload + + if "extraPayloadSize" not in data or "extraPayload" not in data: + raise ValueError("Payload data missing in DIP2 transaction") + + if data["extraPayloadSize"] * 2 != len(data["extraPayload"]): + raise ValueError("length mismatch") + t.extra_data = pack_varint(data["extraPayloadSize"]) + binascii.unhexlify( + data["extraPayload"] + ) + + # Trezor (and therefore KeepKey) firmware doesn't understand the + # split of version and type, so let's mimic the old serialization + # format + t.version |= dip2_type << 16 + + return t + + def get_raw_tx(self, txhash): + data = self.fetch_json(self.url, 'rawtx', txhash)['rawtx'] + return data + + +TxApiBitcoin = TxApiInsight(network='insight_bitcoin', url='https://btc.coinquery.com/api') +TxApiTestnet = TxApiInsight(network='insight_testnet', url='https://test-insight.bitpay.com/api') +TxApiZcashTestnet = TxApiInsight(network='insight_zcashtestnet', url='https://explorer.testnet.z.cash/api', zcash=True) +TxApiBitcoinGold = TxApiInsight(network='insight_bitcoingold', url='https://btg.coinquery.com/api') +TxApiGroestlcoin = TxApiInsight(network='insight_groestlcoin', url='https://groestlsight.groestlcoin.org/api') +TxApiGroestlcoinTestnet = TxApiInsight(network='insight_groestlcoin_testnet', url='https://groestlsight-test.groestlcoin.org/api') +TxApiDash = TxApiInsight(network='insight_dash', url='https://dash.coinquery.com/api') diff --git a/electrum/plugins/keepkey/keepkeylib/types_pb2.py b/electrum/plugins/keepkey/keepkeylib/types_pb2.py new file mode 100644 index 000000000000..6be4ae018607 --- /dev/null +++ b/electrum/plugins/keepkey/keepkeylib/types_pb2.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: types.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 +from . import exchange_pb2 as exchange__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0btypes.proto\x1a google/protobuf/descriptor.proto\x1a\x0e\x65xchange.proto\"\x80\x01\n\nHDNodeType\x12\r\n\x05\x64\x65pth\x18\x01 \x02(\r\x12\x13\n\x0b\x66ingerprint\x18\x02 \x02(\r\x12\x11\n\tchild_num\x18\x03 \x02(\r\x12\x12\n\nchain_code\x18\x04 \x02(\x0c\x12\x13\n\x0bprivate_key\x18\x05 \x01(\x0c\x12\x12\n\npublic_key\x18\x06 \x01(\x0c\">\n\x0eHDNodePathType\x12\x19\n\x04node\x18\x01 \x02(\x0b\x32\x0b.HDNodeType\x12\x11\n\taddress_n\x18\x02 \x03(\r\"\xe8\x03\n\x08\x43oinType\x12\x11\n\tcoin_name\x18\x01 \x01(\t\x12\x15\n\rcoin_shortcut\x18\x02 \x01(\t\x12\x17\n\x0c\x61\x64\x64ress_type\x18\x03 \x01(\r:\x01\x30\x12\x11\n\tmaxfee_kb\x18\x04 \x01(\x04\x12\x1c\n\x11\x61\x64\x64ress_type_p2sh\x18\x05 \x01(\r:\x01\x35\x12\x1d\n\x15signed_message_header\x18\x08 \x01(\t\x12\x1a\n\x12\x62ip44_account_path\x18\t \x01(\r\x12\x0e\n\x06\x66orkid\x18\x0c \x01(\r\x12\x10\n\x08\x64\x65\x63imals\x18\r \x01(\r\x12\x18\n\x10\x63ontract_address\x18\x0e \x01(\x0c\x12\x1c\n\nxpub_magic\x18\x10 \x01(\r:\x08\x37\x36\x30\x36\x37\x33\x35\x38\x12\x0e\n\x06segwit\x18\x12 \x01(\x08\x12\x14\n\x0c\x66orce_bip143\x18\x13 \x01(\x08\x12\x12\n\ncurve_name\x18\x14 \x01(\t\x12\x17\n\x0f\x63\x61shaddr_prefix\x18\x15 \x01(\t\x12\x15\n\rbech32_prefix\x18\x16 \x01(\t\x12\x0e\n\x06\x64\x65\x63red\x18\x17 \x01(\x08\x12\x1e\n\x16xpub_magic_segwit_p2sh\x18\x19 \x01(\r\x12 \n\x18xpub_magic_segwit_native\x18\x1a \x01(\r\x12\x17\n\x0fnanoaddr_prefix\x18\x1b \x01(\t\"[\n\x18MultisigRedeemScriptType\x12 \n\x07pubkeys\x18\x01 \x03(\x0b\x32\x0f.HDNodePathType\x12\x12\n\nsignatures\x18\x02 \x03(\x0c\x12\t\n\x01m\x18\x03 \x01(\r\"\x9f\x02\n\x0bTxInputType\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x11\n\tprev_hash\x18\x02 \x02(\x0c\x12\x12\n\nprev_index\x18\x03 \x02(\r\x12\x12\n\nscript_sig\x18\x04 \x01(\x0c\x12\x1c\n\x08sequence\x18\x05 \x01(\r:\n4294967295\x12\x33\n\x0bscript_type\x18\x06 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS\x12+\n\x08multisig\x18\x07 \x01(\x0b\x32\x19.MultisigRedeemScriptType\x12\x0e\n\x06\x61mount\x18\x08 \x01(\x04\x12\x13\n\x0b\x64\x65\x63red_tree\x18\t \x01(\r\x12\x1d\n\x15\x64\x65\x63red_script_version\x18\n \x01(\r\"\x9e\x02\n\x0cTxOutputType\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x11\n\taddress_n\x18\x02 \x03(\r\x12\x0e\n\x06\x61mount\x18\x03 \x02(\x04\x12&\n\x0bscript_type\x18\x04 \x02(\x0e\x32\x11.OutputScriptType\x12+\n\x08multisig\x18\x05 \x01(\x0b\x32\x19.MultisigRedeemScriptType\x12\x16\n\x0eop_return_data\x18\x06 \x01(\x0c\x12(\n\x0c\x61\x64\x64ress_type\x18\x07 \x01(\x0e\x32\x12.OutputAddressType\x12$\n\rexchange_type\x18\x08 \x01(\x0b\x32\r.ExchangeType\x12\x1d\n\x15\x64\x65\x63red_script_version\x18\t \x01(\r\"W\n\x0fTxOutputBinType\x12\x0e\n\x06\x61mount\x18\x01 \x02(\x04\x12\x15\n\rscript_pubkey\x18\x02 \x02(\x0c\x12\x1d\n\x15\x64\x65\x63red_script_version\x18\x03 \x01(\r\"\xc2\x02\n\x0fTransactionType\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x1c\n\x06inputs\x18\x02 \x03(\x0b\x32\x0c.TxInputType\x12%\n\x0b\x62in_outputs\x18\x03 \x03(\x0b\x32\x10.TxOutputBinType\x12\x1e\n\x07outputs\x18\x05 \x03(\x0b\x32\r.TxOutputType\x12\x11\n\tlock_time\x18\x04 \x01(\r\x12\x12\n\ninputs_cnt\x18\x06 \x01(\r\x12\x13\n\x0boutputs_cnt\x18\x07 \x01(\r\x12\x12\n\nextra_data\x18\x08 \x01(\x0c\x12\x16\n\x0e\x65xtra_data_len\x18\t \x01(\r\x12\x0e\n\x06\x65xpiry\x18\n \x01(\r\x12\x14\n\x0coverwintered\x18\x0b \x01(\x08\x12\x18\n\x10version_group_id\x18\x0c \x01(\r\x12\x11\n\tbranch_id\x18\r \x01(\r\"%\n\x12RawTransactionType\x12\x0f\n\x07payload\x18\x01 \x02(\x0c\"q\n\x14TxRequestDetailsType\x12\x15\n\rrequest_index\x18\x01 \x01(\r\x12\x0f\n\x07tx_hash\x18\x02 \x01(\x0c\x12\x16\n\x0e\x65xtra_data_len\x18\x03 \x01(\r\x12\x19\n\x11\x65xtra_data_offset\x18\x04 \x01(\r\"\\\n\x17TxRequestSerializedType\x12\x17\n\x0fsignature_index\x18\x01 \x01(\r\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x15\n\rserialized_tx\x18\x03 \x01(\x0c\"g\n\x0cIdentityType\x12\r\n\x05proto\x18\x01 \x01(\t\x12\x0c\n\x04user\x18\x02 \x01(\t\x12\x0c\n\x04host\x18\x03 \x01(\t\x12\x0c\n\x04port\x18\x04 \x01(\t\x12\x0c\n\x04path\x18\x05 \x01(\t\x12\x10\n\x05index\x18\x06 \x01(\r:\x01\x30\"2\n\nPolicyType\x12\x13\n\x0bpolicy_name\x18\x01 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x02 \x01(\x08\"\xa4\x02\n\x0c\x45xchangeType\x12\x39\n\x18signed_exchange_response\x18\x01 \x01(\x0b\x32\x17.SignedExchangeResponse\x12%\n\x14withdrawal_coin_name\x18\x02 \x01(\t:\x07\x42itcoin\x12\x1c\n\x14withdrawal_address_n\x18\x03 \x03(\r\x12\x18\n\x10return_address_n\x18\x04 \x03(\r\x12>\n\x16withdrawal_script_type\x18\x05 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS\x12:\n\x12return_script_type\x18\x06 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS*\xe6\x02\n\x0b\x46\x61ilureType\x12\x1d\n\x19\x46\x61ilure_UnexpectedMessage\x10\x01\x12\x1a\n\x16\x46\x61ilure_ButtonExpected\x10\x02\x12\x17\n\x13\x46\x61ilure_SyntaxError\x10\x03\x12\x1b\n\x17\x46\x61ilure_ActionCancelled\x10\x04\x12\x17\n\x13\x46\x61ilure_PinExpected\x10\x05\x12\x18\n\x14\x46\x61ilure_PinCancelled\x10\x06\x12\x16\n\x12\x46\x61ilure_PinInvalid\x10\x07\x12\x1c\n\x18\x46\x61ilure_InvalidSignature\x10\x08\x12\x11\n\rFailure_Other\x10\t\x12\x1a\n\x16\x46\x61ilure_NotEnoughFunds\x10\n\x12\x1a\n\x16\x46\x61ilure_NotInitialized\x10\x0b\x12\x17\n\x13\x46\x61ilure_PinMismatch\x10\x0c\x12\x19\n\x15\x46\x61ilure_FirmwareError\x10\x63*\x87\x01\n\x10OutputScriptType\x12\x10\n\x0cPAYTOADDRESS\x10\x00\x12\x13\n\x0fPAYTOSCRIPTHASH\x10\x01\x12\x11\n\rPAYTOMULTISIG\x10\x02\x12\x11\n\rPAYTOOPRETURN\x10\x03\x12\x10\n\x0cPAYTOWITNESS\x10\x04\x12\x14\n\x10PAYTOP2SHWITNESS\x10\x05*l\n\x0fInputScriptType\x12\x10\n\x0cSPENDADDRESS\x10\x00\x12\x11\n\rSPENDMULTISIG\x10\x01\x12\x0c\n\x08\x45XTERNAL\x10\x02\x12\x10\n\x0cSPENDWITNESS\x10\x03\x12\x14\n\x10SPENDP2SHWITNESS\x10\x04*U\n\x0bRequestType\x12\x0b\n\x07TXINPUT\x10\x00\x12\x0c\n\x08TXOUTPUT\x10\x01\x12\n\n\x06TXMETA\x10\x02\x12\x0e\n\nTXFINISHED\x10\x03\x12\x0f\n\x0bTXEXTRADATA\x10\x04*F\n\x11OutputAddressType\x12\t\n\x05SPEND\x10\x00\x12\x0c\n\x08TRANSFER\x10\x01\x12\n\n\x06\x43HANGE\x10\x02\x12\x0c\n\x08\x45XCHANGE\x10\x03*\x94\t\n\x11\x42uttonRequestType\x12\x17\n\x13\x42uttonRequest_Other\x10\x01\x12\"\n\x1e\x42uttonRequest_FeeOverThreshold\x10\x02\x12\x1f\n\x1b\x42uttonRequest_ConfirmOutput\x10\x03\x12\x1d\n\x19\x42uttonRequest_ResetDevice\x10\x04\x12\x1d\n\x19\x42uttonRequest_ConfirmWord\x10\x05\x12\x1c\n\x18\x42uttonRequest_WipeDevice\x10\x06\x12\x1d\n\x19\x42uttonRequest_ProtectCall\x10\x07\x12\x18\n\x14\x42uttonRequest_SignTx\x10\x08\x12\x1f\n\x1b\x42uttonRequest_FirmwareCheck\x10\t\x12\x19\n\x15\x42uttonRequest_Address\x10\n\x12\x1f\n\x1b\x42uttonRequest_FirmwareErase\x10\x0b\x12*\n&ButtonRequest_ConfirmTransferToAccount\x10\x0c\x12+\n\'ButtonRequest_ConfirmTransferToNodePath\x10\r\x12\x1d\n\x19\x42uttonRequest_ChangeLabel\x10\x0e\x12 \n\x1c\x42uttonRequest_ChangeLanguage\x10\x0f\x12\"\n\x1e\x42uttonRequest_EnablePassphrase\x10\x10\x12#\n\x1f\x42uttonRequest_DisablePassphrase\x10\x11\x12\'\n#ButtonRequest_EncryptAndSignMessage\x10\x12\x12 \n\x1c\x42uttonRequest_EncryptMessage\x10\x13\x12\"\n\x1e\x42uttonRequest_ImportPrivateKey\x10\x14\x12(\n$ButtonRequest_ImportRecoverySentence\x10\x15\x12\x1e\n\x1a\x42uttonRequest_SignIdentity\x10\x16\x12\x16\n\x12\x42uttonRequest_Ping\x10\x17\x12\x1b\n\x17\x42uttonRequest_RemovePin\x10\x18\x12\x1b\n\x17\x42uttonRequest_ChangePin\x10\x19\x12\x1b\n\x17\x42uttonRequest_CreatePin\x10\x1a\x12\x1c\n\x18\x42uttonRequest_GetEntropy\x10\x1b\x12\x1d\n\x19\x42uttonRequest_SignMessage\x10\x1c\x12\x1f\n\x1b\x42uttonRequest_ApplyPolicies\x10\x1d\x12\x1e\n\x1a\x42uttonRequest_SignExchange\x10\x1e\x12!\n\x1d\x42uttonRequest_AutoLockDelayMs\x10\x1f\x12\x1c\n\x18\x42uttonRequest_U2FCounter\x10 \x12\"\n\x1e\x42uttonRequest_ConfirmEosAction\x10!\x12\"\n\x1e\x42uttonRequest_ConfirmEosBudget\x10\"\x12\x1d\n\x19\x42uttonRequest_ConfirmMemo\x10#*\x7f\n\x14PinMatrixRequestType\x12 \n\x1cPinMatrixRequestType_Current\x10\x01\x12!\n\x1dPinMatrixRequestType_NewFirst\x10\x02\x12\"\n\x1ePinMatrixRequestType_NewSecond\x10\x03:4\n\x07wire_in\x12!.google.protobuf.EnumValueOptions\x18\xd2\x86\x03 \x01(\x08:5\n\x08wire_out\x12!.google.protobuf.EnumValueOptions\x18\xd3\x86\x03 \x01(\x08::\n\rwire_debug_in\x12!.google.protobuf.EnumValueOptions\x18\xd4\x86\x03 \x01(\x08:;\n\x0ewire_debug_out\x12!.google.protobuf.EnumValueOptions\x18\xd5\x86\x03 \x01(\x08\x42)\n\x1a\x63om.keepkey.deviceprotocolB\x0bKeepKeyType') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'types_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + google_dot_protobuf_dot_descriptor__pb2.EnumValueOptions.RegisterExtension(wire_in) + google_dot_protobuf_dot_descriptor__pb2.EnumValueOptions.RegisterExtension(wire_out) + google_dot_protobuf_dot_descriptor__pb2.EnumValueOptions.RegisterExtension(wire_debug_in) + google_dot_protobuf_dot_descriptor__pb2.EnumValueOptions.RegisterExtension(wire_debug_out) + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'\n\032com.keepkey.deviceprotocolB\013KeepKeyType' + _FAILURETYPE._serialized_start=2538 + _FAILURETYPE._serialized_end=2896 + _OUTPUTSCRIPTTYPE._serialized_start=2899 + _OUTPUTSCRIPTTYPE._serialized_end=3034 + _INPUTSCRIPTTYPE._serialized_start=3036 + _INPUTSCRIPTTYPE._serialized_end=3144 + _REQUESTTYPE._serialized_start=3146 + _REQUESTTYPE._serialized_end=3231 + _OUTPUTADDRESSTYPE._serialized_start=3233 + _OUTPUTADDRESSTYPE._serialized_end=3303 + _BUTTONREQUESTTYPE._serialized_start=3306 + _BUTTONREQUESTTYPE._serialized_end=4478 + _PINMATRIXREQUESTTYPE._serialized_start=4480 + _PINMATRIXREQUESTTYPE._serialized_end=4607 + _HDNODETYPE._serialized_start=66 + _HDNODETYPE._serialized_end=194 + _HDNODEPATHTYPE._serialized_start=196 + _HDNODEPATHTYPE._serialized_end=258 + _COINTYPE._serialized_start=261 + _COINTYPE._serialized_end=749 + _MULTISIGREDEEMSCRIPTTYPE._serialized_start=751 + _MULTISIGREDEEMSCRIPTTYPE._serialized_end=842 + _TXINPUTTYPE._serialized_start=845 + _TXINPUTTYPE._serialized_end=1132 + _TXOUTPUTTYPE._serialized_start=1135 + _TXOUTPUTTYPE._serialized_end=1421 + _TXOUTPUTBINTYPE._serialized_start=1423 + _TXOUTPUTBINTYPE._serialized_end=1510 + _TRANSACTIONTYPE._serialized_start=1513 + _TRANSACTIONTYPE._serialized_end=1835 + _RAWTRANSACTIONTYPE._serialized_start=1837 + _RAWTRANSACTIONTYPE._serialized_end=1874 + _TXREQUESTDETAILSTYPE._serialized_start=1876 + _TXREQUESTDETAILSTYPE._serialized_end=1989 + _TXREQUESTSERIALIZEDTYPE._serialized_start=1991 + _TXREQUESTSERIALIZEDTYPE._serialized_end=2083 + _IDENTITYTYPE._serialized_start=2085 + _IDENTITYTYPE._serialized_end=2188 + _POLICYTYPE._serialized_start=2190 + _POLICYTYPE._serialized_end=2240 + _EXCHANGETYPE._serialized_start=2243 + _EXCHANGETYPE._serialized_end=2535 +# @@protoc_insertion_point(module_scope)