|
|
|
@ -1,4 +1,4 @@ |
|
|
|
import cbor |
|
|
|
import cbor2 as cbor |
|
|
|
import hashlib |
|
|
|
import hashlib |
|
|
|
import json |
|
|
|
import json |
|
|
|
import time |
|
|
|
import time |
|
|
|
@ -17,20 +17,19 @@ from .jade_serial import JadeSerialImpl |
|
|
|
from .jade_tcp import JadeTCPImpl |
|
|
|
from .jade_tcp import JadeTCPImpl |
|
|
|
|
|
|
|
|
|
|
|
# 'jade' logger |
|
|
|
# 'jade' logger |
|
|
|
logger = logging.getLogger('jade') |
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
device_logger = logging.getLogger('jade-device') |
|
|
|
device_logger = logging.getLogger(f'{__name__}-device') |
|
|
|
|
|
|
|
|
|
|
|
# BLE comms backend is optional |
|
|
|
# BLE comms backend is optional |
|
|
|
# It relies on the BLE dependencies being available |
|
|
|
# It relies on the BLE dependencies being available |
|
|
|
try: |
|
|
|
try: |
|
|
|
from .jade_ble import JadeBleImpl |
|
|
|
from .jade_ble import JadeBleImpl |
|
|
|
except ImportError as e: |
|
|
|
except ImportError as e: |
|
|
|
logger.warn(e) |
|
|
|
logger.warning(e) |
|
|
|
logger.warn('BLE scanning/connectivity will not be available') |
|
|
|
logger.warning('BLE scanning/connectivity will not be available') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Default serial connection |
|
|
|
# Default serial connection |
|
|
|
DEFAULT_SERIAL_DEVICE = '/dev/ttyUSB0' |
|
|
|
|
|
|
|
DEFAULT_BAUD_RATE = 115200 |
|
|
|
DEFAULT_BAUD_RATE = 115200 |
|
|
|
DEFAULT_SERIAL_TIMEOUT = 120 |
|
|
|
DEFAULT_SERIAL_TIMEOUT = 120 |
|
|
|
|
|
|
|
|
|
|
|
@ -77,7 +76,7 @@ def _hexlify(data): |
|
|
|
# The default implementation used in JadeAPI._jadeRpc() below. |
|
|
|
# The default implementation used in JadeAPI._jadeRpc() below. |
|
|
|
# NOTE: Only available if the 'requests' dependency is available. |
|
|
|
# NOTE: Only available if the 'requests' dependency is available. |
|
|
|
# |
|
|
|
# |
|
|
|
# Callers can supply their own implementation of this call where it is required. |
|
|
|
# Callers can supply their own implmentation of this call where it is required. |
|
|
|
# |
|
|
|
# |
|
|
|
# Parameters |
|
|
|
# Parameters |
|
|
|
# ---------- |
|
|
|
# ---------- |
|
|
|
@ -94,28 +93,35 @@ def _hexlify(data): |
|
|
|
# |
|
|
|
# |
|
|
|
# # Use the first non-onion url |
|
|
|
# # Use the first non-onion url |
|
|
|
# url = [url for url in params['urls'] if not url.endswith('.onion')][0] |
|
|
|
# url = [url for url in params['urls'] if not url.endswith('.onion')][0] |
|
|
|
|
|
|
|
# |
|
|
|
# if params['method'] == 'GET': |
|
|
|
# if params['method'] == 'GET': |
|
|
|
# assert 'data' not in params, 'Cannot pass body to requests.get' |
|
|
|
# assert 'data' not in params, 'Cannot pass body to requests.get' |
|
|
|
# f = requests.get(url) |
|
|
|
# def http_call_fn(): return requests.get(url) |
|
|
|
# elif params['method'] == 'POST': |
|
|
|
# elif params['method'] == 'POST': |
|
|
|
# data = json.dumps(params['data']) |
|
|
|
# data = json.dumps(params['data']) |
|
|
|
# f = requests.post(url, data) |
|
|
|
# def http_call_fn(): return requests.post(url, data) |
|
|
|
|
|
|
|
# else: |
|
|
|
|
|
|
|
# raise JadeError(1, "Only GET and POST methods supported", params['method']) |
|
|
|
# |
|
|
|
# |
|
|
|
# logger.debug("http_request received reply: {}".format(f.text)) |
|
|
|
# try: |
|
|
|
|
|
|
|
# f = http_call_fn() |
|
|
|
|
|
|
|
# logger.debug("http_request received reply: {}".format(f.text)) |
|
|
|
# |
|
|
|
# |
|
|
|
# if f.status_code != 200: |
|
|
|
# if f.status_code != 200: |
|
|
|
# logger.error("http error {} : {}".format(f.status_code, f.text)) |
|
|
|
# logger.error("http error {} : {}".format(f.status_code, f.text)) |
|
|
|
# raise ValueError(f.status_code) |
|
|
|
# raise ValueError(f.status_code) |
|
|
|
# |
|
|
|
# |
|
|
|
# assert params['accept'] == 'json' |
|
|
|
# assert params['accept'] == 'json' |
|
|
|
# f = f.json() |
|
|
|
# f = f.json() |
|
|
|
|
|
|
|
# except Exception as e: |
|
|
|
|
|
|
|
# logging.error(e) |
|
|
|
|
|
|
|
# f = None |
|
|
|
# |
|
|
|
# |
|
|
|
# return {'body': f} |
|
|
|
# return {'body': f} |
|
|
|
# |
|
|
|
# |
|
|
|
# except ImportError as e: |
|
|
|
# except ImportError as e: |
|
|
|
# logger.warn(e) |
|
|
|
# logger.info(e) |
|
|
|
# logger.warn('Default _http_requests() function will not be available') |
|
|
|
# logger.info('Default _http_requests() function will not be available') |
|
|
|
# |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class JadeAPI: |
|
|
|
class JadeAPI: |
|
|
|
""" |
|
|
|
""" |
|
|
|
@ -144,9 +150,9 @@ class JadeAPI: |
|
|
|
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc, tb): |
|
|
|
def __exit__(self, exc_type, exc, tb): |
|
|
|
if (exc_type): |
|
|
|
if (exc_type): |
|
|
|
logger.error("Exception causing JadeAPI context exit.") |
|
|
|
logger.info("Exception causing JadeAPI context exit.") |
|
|
|
logger.error(exc_type) |
|
|
|
logger.info(exc_type) |
|
|
|
logger.error(exc) |
|
|
|
logger.info(exc) |
|
|
|
traceback.print_tb(tb) |
|
|
|
traceback.print_tb(tb) |
|
|
|
self.disconnect(exc_type is not None) |
|
|
|
self.disconnect(exc_type is not None) |
|
|
|
|
|
|
|
|
|
|
|
@ -340,16 +346,41 @@ class JadeAPI: |
|
|
|
|
|
|
|
|
|
|
|
return result |
|
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
def get_version_info(self): |
|
|
|
def ping(self): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
RPC call to test the connection to Jade and that Jade is powered on and receiving data, and |
|
|
|
|
|
|
|
return whether the main task is currently handling a message, handling user menu navigation |
|
|
|
|
|
|
|
or is idle. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NOTE: unlike all other calls this is not queued and handled in fifo order - this message is |
|
|
|
|
|
|
|
handled immediately and the response sent as quickly as possible. This call does not block. |
|
|
|
|
|
|
|
If this call is made in parallel with Jade processing other messages, the replies may be |
|
|
|
|
|
|
|
out of order (although the message 'id' should still be correct). Use with caution. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
|
|
|
|
------- |
|
|
|
|
|
|
|
0 if the main task is currently idle |
|
|
|
|
|
|
|
1 if the main task is handling a client message |
|
|
|
|
|
|
|
2 if the main task is handling user ui menu navigation |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
return self._jadeRpc('ping') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_version_info(self, nonblocking=False): |
|
|
|
""" |
|
|
|
""" |
|
|
|
RPC call to fetch summary details pertaining to the hardware unit and running firmware. |
|
|
|
RPC call to fetch summary details pertaining to the hardware unit and running firmware. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
|
|
|
|
---------- |
|
|
|
|
|
|
|
nonblocking : bool |
|
|
|
|
|
|
|
If True message will be handled immediately (see also ping()) *experimental feature* |
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
Returns |
|
|
|
------- |
|
|
|
------- |
|
|
|
dict |
|
|
|
dict |
|
|
|
Contains keys for various info describing the hw and running fw |
|
|
|
Contains keys for various info describing the hw and running fw |
|
|
|
""" |
|
|
|
""" |
|
|
|
return self._jadeRpc('get_version_info') |
|
|
|
params = {'nonblocking': True} if nonblocking else None |
|
|
|
|
|
|
|
return self._jadeRpc('get_version_info', params) |
|
|
|
|
|
|
|
|
|
|
|
def add_entropy(self, entropy): |
|
|
|
def add_entropy(self, entropy): |
|
|
|
""" |
|
|
|
""" |
|
|
|
@ -387,7 +418,20 @@ class JadeAPI: |
|
|
|
params = {'epoch': epoch if epoch is not None else int(time.time())} |
|
|
|
params = {'epoch': epoch if epoch is not None else int(time.time())} |
|
|
|
return self._jadeRpc('set_epoch', params) |
|
|
|
return self._jadeRpc('set_epoch', params) |
|
|
|
|
|
|
|
|
|
|
|
def ota_update(self, fwcmp, fwlen, chunksize, patchlen=None, cb=None): |
|
|
|
def logout(self): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
RPC call to logout of any wallet loaded on the Jade unit. |
|
|
|
|
|
|
|
Any key material is freed and zero'd. |
|
|
|
|
|
|
|
Call always returns true. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
|
|
|
|
------- |
|
|
|
|
|
|
|
bool |
|
|
|
|
|
|
|
True |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
return self._jadeRpc('logout') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ota_update(self, fwcmp, fwlen, chunksize, fwhash=None, patchlen=None, cb=None): |
|
|
|
""" |
|
|
|
""" |
|
|
|
RPC call to attempt to update the unit's firmware. |
|
|
|
RPC call to attempt to update the unit's firmware. |
|
|
|
|
|
|
|
|
|
|
|
@ -403,6 +447,12 @@ class JadeAPI: |
|
|
|
and ack'd by the hw unit. |
|
|
|
and ack'd by the hw unit. |
|
|
|
The maximum supported chunk size is given in the version info data, under the key |
|
|
|
The maximum supported chunk size is given in the version info data, under the key |
|
|
|
'JADE_OTA_MAX_CHUNK'. |
|
|
|
'JADE_OTA_MAX_CHUNK'. |
|
|
|
|
|
|
|
fwhash: 32-bytes, optional |
|
|
|
|
|
|
|
The sha256 hash of the full uncompressed final firmware image. In the case of a full |
|
|
|
|
|
|
|
firmware upload this should be the hash of the uncompressed file. In the case of a |
|
|
|
|
|
|
|
delta update this is the hash of the expected final image - ie. the existing firmware |
|
|
|
|
|
|
|
with the uploaded delta applied. ie. it is a verification of the fw image Jade will try |
|
|
|
|
|
|
|
to boot. Optional for backward-compatibility - may become mandatory in a future release. |
|
|
|
patchlen: int, optional |
|
|
|
patchlen: int, optional |
|
|
|
If the compressed firmware bytes are an incremental diff to be applied to the running |
|
|
|
If the compressed firmware bytes are an incremental diff to be applied to the running |
|
|
|
firmware image, this is the size of that patch when uncompressed. |
|
|
|
firmware image, this is the size of that patch when uncompressed. |
|
|
|
@ -434,6 +484,9 @@ class JadeAPI: |
|
|
|
'cmpsize': cmplen, |
|
|
|
'cmpsize': cmplen, |
|
|
|
'cmphash': cmphash} |
|
|
|
'cmphash': cmphash} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if fwhash is not None: |
|
|
|
|
|
|
|
params['fwhash'] = fwhash |
|
|
|
|
|
|
|
|
|
|
|
if patchlen is not None: |
|
|
|
if patchlen is not None: |
|
|
|
ota_method = 'ota_delta' |
|
|
|
ota_method = 'ota_delta' |
|
|
|
params['patchsize'] = patchlen |
|
|
|
params['patchsize'] = patchlen |
|
|
|
@ -464,11 +517,53 @@ class JadeAPI: |
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
Returns |
|
|
|
------- |
|
|
|
------- |
|
|
|
bool |
|
|
|
int |
|
|
|
True on success. |
|
|
|
Time in ms for the internal tests to run, as measured on the hw. |
|
|
|
|
|
|
|
ie. excluding any messaging overhead |
|
|
|
""" |
|
|
|
""" |
|
|
|
return self._jadeRpc('debug_selfcheck', long_timeout=True) |
|
|
|
return self._jadeRpc('debug_selfcheck', long_timeout=True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def capture_image_data(self, check_qr=False): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
RPC call to capture raw image data from the camera. |
|
|
|
|
|
|
|
See also scan_qr() below. |
|
|
|
|
|
|
|
NOTE: Only available in a DEBUG build of the firmware. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
|
|
|
|
---------- |
|
|
|
|
|
|
|
check_qr : bool, optional |
|
|
|
|
|
|
|
If True only images which contain a valid qr code are captured and returned. |
|
|
|
|
|
|
|
If False, any image is considered valid and is returned. |
|
|
|
|
|
|
|
Defaults to False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
|
|
|
|
------- |
|
|
|
|
|
|
|
bytes |
|
|
|
|
|
|
|
Raw image data from the camera framebuffer |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
params = {'check_qr': check_qr} |
|
|
|
|
|
|
|
return self._jadeRpc('debug_capture_image_data', params) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def scan_qr(self, image): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
RPC call to scan a passed image and return any data extracted from any qr image. |
|
|
|
|
|
|
|
Exercises the camera image capture, but ignores result and uses passed image instead. |
|
|
|
|
|
|
|
See also capture_image_data() above. |
|
|
|
|
|
|
|
NOTE: Only available in a DEBUG build of the firmware. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
|
|
|
|
---------- |
|
|
|
|
|
|
|
image : bytes |
|
|
|
|
|
|
|
The image data (as obtained from capture_image_data() above). |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
|
|
|
|
------- |
|
|
|
|
|
|
|
bytes |
|
|
|
|
|
|
|
String or byte data obtained from the image (via qr code) |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
params = {'image': image} |
|
|
|
|
|
|
|
return self._jadeRpc('debug_scan_qr', params) |
|
|
|
|
|
|
|
|
|
|
|
def clean_reset(self): |
|
|
|
def clean_reset(self): |
|
|
|
""" |
|
|
|
""" |
|
|
|
RPC call to clean/reset memory and storage, as much as is practical. |
|
|
|
RPC call to clean/reset memory and storage, as much as is practical. |
|
|
|
@ -529,6 +624,38 @@ class JadeAPI: |
|
|
|
params = {'seed': seed} |
|
|
|
params = {'seed': seed} |
|
|
|
return self._jadeRpc('debug_set_mnemonic', params) |
|
|
|
return self._jadeRpc('debug_set_mnemonic', params) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_bip85_bip39_entropy(self, num_words, index, pubkey): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
RPC call to fetch encrypted bip85-bip39 entropy. |
|
|
|
|
|
|
|
NOTE: Only available in a DEBUG build of the firmware. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
|
|
|
|
---------- |
|
|
|
|
|
|
|
num_words : int |
|
|
|
|
|
|
|
The number of words the entropy is required to produce. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index : int |
|
|
|
|
|
|
|
The index to use in the bip32 path to calcuate the entropy. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pubkey: 33-bytes |
|
|
|
|
|
|
|
The host ephemeral pubkey to use to generate a shared ecdh secret to use as an AES key |
|
|
|
|
|
|
|
to encrypt the returned entropy. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
|
|
|
|
------- |
|
|
|
|
|
|
|
dict |
|
|
|
|
|
|
|
pubkey - 33-bytes, Jade's ephemeral pubkey used to generate a shared ecdh secret used as |
|
|
|
|
|
|
|
an AES key to encrypt the returned entropy |
|
|
|
|
|
|
|
encrypted - bytes, the requested bip85 bip39 entropy, AES encrypted with the first key |
|
|
|
|
|
|
|
derived from the ecdh shared secret, prefixed with the iv |
|
|
|
|
|
|
|
hmac - 32-bytes, the hmac of the encrypted buffer, using the second key derived from the |
|
|
|
|
|
|
|
ecdh shared secret |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
params = {'num_words': num_words, |
|
|
|
|
|
|
|
'index': index, |
|
|
|
|
|
|
|
'pubkey': pubkey} |
|
|
|
|
|
|
|
return self._jadeRpc('get_bip85_bip39_entropy', params) |
|
|
|
|
|
|
|
|
|
|
|
def set_pinserver(self, urlA=None, urlB=None, pubkey=None, cert=None): |
|
|
|
def set_pinserver(self, urlA=None, urlB=None, pubkey=None, cert=None): |
|
|
|
""" |
|
|
|
""" |
|
|
|
RPC call to explicitly set (override) the details of the blind pinserver used to |
|
|
|
RPC call to explicitly set (override) the details of the blind pinserver used to |
|
|
|
@ -698,6 +825,55 @@ class JadeAPI: |
|
|
|
""" |
|
|
|
""" |
|
|
|
return self._jadeRpc('get_registered_multisigs') |
|
|
|
return self._jadeRpc('get_registered_multisigs') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_registered_multisig(self, multisig_name, as_file=False): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
RPC call to fetch details of a named multisig wallet registered to this signer. |
|
|
|
|
|
|
|
NOTE: the multisig wallet must have been registered with firmware v1.0.23 or later |
|
|
|
|
|
|
|
for the full signer details to be persisted and available. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
|
|
|
|
---------- |
|
|
|
|
|
|
|
multisig_name : string |
|
|
|
|
|
|
|
Name of multsig registration record to return. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
as_file : string, optional |
|
|
|
|
|
|
|
If true the flat file format is returned, otherwise structured json is returned. |
|
|
|
|
|
|
|
Defaults to false. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
|
|
|
|
------- |
|
|
|
|
|
|
|
dict |
|
|
|
|
|
|
|
Description of registered multisig wallet identified by registration name. |
|
|
|
|
|
|
|
Contains keys: |
|
|
|
|
|
|
|
is_file is true: |
|
|
|
|
|
|
|
multisig_file - str, the multisig file as produced by several wallet apps. |
|
|
|
|
|
|
|
eg: |
|
|
|
|
|
|
|
Name: MainWallet |
|
|
|
|
|
|
|
Policy: 2 of 3 |
|
|
|
|
|
|
|
Format: P2WSH |
|
|
|
|
|
|
|
Derivation: m/48'/0'/0'/2' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
B237FE9D: xpub6E8C7BX4c7qfTsX7urnXggcAyFuhDmYLQhwRwZGLD9maUGWPinuc9k96ej... |
|
|
|
|
|
|
|
249192D2: xpub6EbXynW6xjYR3crcztum6KzSWqDJoAJQoovwamwVnLaCSHA6syXKPnJo6U... |
|
|
|
|
|
|
|
67F90FFC: xpub6EHuWWrYd8bp5FS1XAZsMPkmCqLSjpULmygWqAqWRCCjSWQwz6ntq5KnuQ... |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
is_file is false: |
|
|
|
|
|
|
|
multisig_name - str, name of multisig registration |
|
|
|
|
|
|
|
variant - str, script type, eg. 'sh(wsh(multi(k)))' |
|
|
|
|
|
|
|
sorted - boolean, whether bip67 key sorting is applied |
|
|
|
|
|
|
|
threshold - int, number of signers required,N |
|
|
|
|
|
|
|
master_blinding_key - 32-bytes, any liquid master blinding key for this wallet |
|
|
|
|
|
|
|
signers - dict containing keys: |
|
|
|
|
|
|
|
fingerprint - 4 bytes, origin fingerprint |
|
|
|
|
|
|
|
derivation - [int], bip32 path from origin to signer xpub provided |
|
|
|
|
|
|
|
xpub - str, base58 xpub of signer |
|
|
|
|
|
|
|
path - [int], any fixed path to always apply after the xpub - usually empty. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
params = {'multisig_name': multisig_name, |
|
|
|
|
|
|
|
'as_file': as_file} |
|
|
|
|
|
|
|
return self._jadeRpc('get_registered_multisig', params) |
|
|
|
|
|
|
|
|
|
|
|
def register_multisig(self, network, multisig_name, variant, sorted_keys, threshold, signers, |
|
|
|
def register_multisig(self, network, multisig_name, variant, sorted_keys, threshold, signers, |
|
|
|
master_blinding_key=None): |
|
|
|
master_blinding_key=None): |
|
|
|
""" |
|
|
|
""" |
|
|
|
@ -748,8 +924,59 @@ class JadeAPI: |
|
|
|
'master_blinding_key': master_blinding_key}} |
|
|
|
'master_blinding_key': master_blinding_key}} |
|
|
|
return self._jadeRpc('register_multisig', params) |
|
|
|
return self._jadeRpc('register_multisig', params) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def register_multisig_file(self, multisig_file): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
RPC call to register a new multisig wallet, which must contain the hw signer. |
|
|
|
|
|
|
|
A registration file is provided - as produced my several wallet apps. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
|
|
|
|
---------- |
|
|
|
|
|
|
|
multisig_file : string |
|
|
|
|
|
|
|
The multisig file as produced by several wallet apps. |
|
|
|
|
|
|
|
eg: |
|
|
|
|
|
|
|
Name: MainWallet |
|
|
|
|
|
|
|
Policy: 2 of 3 |
|
|
|
|
|
|
|
Format: P2WSH |
|
|
|
|
|
|
|
Derivation: m/48'/0'/0'/2' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
B237FE9D: xpub6E8C7BX4c7qfTsX7urnXggcAyFuhDmYLQhwRwZGLD9maUGWPinuc9k96ejhEQ1DCk... |
|
|
|
|
|
|
|
249192D2: xpub6EbXynW6xjYR3crcztum6KzSWqDJoAJQoovwamwVnLaCSHA6syXKPnJo6U3bVeGde... |
|
|
|
|
|
|
|
67F90FFC: xpub6EHuWWrYd8bp5FS1XAZsMPkmCqLSjpULmygWqAqWRCCjSWQwz6ntq5KnuQnL23No2... |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
|
|
|
|
------- |
|
|
|
|
|
|
|
bool |
|
|
|
|
|
|
|
True on success, implying the mutisig wallet can now be used. |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
params = {'multisig_file': multisig_file} |
|
|
|
|
|
|
|
return self._jadeRpc('register_multisig', params) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def register_descriptor(self, network, descriptor_name, descriptor_script, datavalues=None): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
RPC call to register a new descriptor wallet, which must contain the hw signer. |
|
|
|
|
|
|
|
A registration name is provided - if it already exists that record is overwritten. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
|
|
|
|
---------- |
|
|
|
|
|
|
|
network : string |
|
|
|
|
|
|
|
Network to which the multisig should apply - eg. 'mainnet', 'liquid', 'testnet', etc. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
descriptor_name : string |
|
|
|
|
|
|
|
Name to use to identify this descriptor wallet registration record. |
|
|
|
|
|
|
|
If a registration record exists with the name given, that record is overwritten. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
|
|
|
|
------- |
|
|
|
|
|
|
|
bool |
|
|
|
|
|
|
|
True on success, implying the descriptor wallet can now be used. |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
params = {'network': network, 'descriptor_name': descriptor_name, |
|
|
|
|
|
|
|
'descriptor': descriptor_script, 'datavalues': datavalues} |
|
|
|
|
|
|
|
return self._jadeRpc('register_descriptor', params) |
|
|
|
|
|
|
|
|
|
|
|
def get_receive_address(self, *args, recovery_xpub=None, csv_blocks=0, |
|
|
|
def get_receive_address(self, *args, recovery_xpub=None, csv_blocks=0, |
|
|
|
variant=None, multisig_name=None, confidential=None): |
|
|
|
variant=None, multisig_name=None, descriptor_name=None, |
|
|
|
|
|
|
|
confidential=None): |
|
|
|
""" |
|
|
|
""" |
|
|
|
RPC call to generate, show, and return an address for the given path. |
|
|
|
RPC call to generate, show, and return an address for the given path. |
|
|
|
The call has three forms. |
|
|
|
The call has three forms. |
|
|
|
@ -795,6 +1022,16 @@ class JadeAPI: |
|
|
|
multisig_name : str |
|
|
|
multisig_name : str |
|
|
|
The name of the registered multisig wallet record used to generate the address. |
|
|
|
The name of the registered multisig wallet record used to generate the address. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4. Descriptor wallet addresses |
|
|
|
|
|
|
|
branch : int |
|
|
|
|
|
|
|
Multi-path derivation branch, usually 0. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pointer : int |
|
|
|
|
|
|
|
Path index to descriptor |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
descriptor_name : str |
|
|
|
|
|
|
|
The name of the registered descriptor wallet record used to generate the address. |
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
Returns |
|
|
|
------- |
|
|
|
------- |
|
|
|
str |
|
|
|
str |
|
|
|
@ -805,6 +1042,10 @@ class JadeAPI: |
|
|
|
assert len(args) == 2 |
|
|
|
assert len(args) == 2 |
|
|
|
keys = ['network', 'paths', 'multisig_name'] |
|
|
|
keys = ['network', 'paths', 'multisig_name'] |
|
|
|
args += (multisig_name,) |
|
|
|
args += (multisig_name,) |
|
|
|
|
|
|
|
elif descriptor_name is not None: |
|
|
|
|
|
|
|
assert len(args) == 3 |
|
|
|
|
|
|
|
keys = ['network', 'branch', 'pointer', 'descriptor_name'] |
|
|
|
|
|
|
|
args += (descriptor_name,) |
|
|
|
elif variant is not None: |
|
|
|
elif variant is not None: |
|
|
|
assert len(args) == 2 |
|
|
|
assert len(args) == 2 |
|
|
|
keys = ['network', 'path', 'variant'] |
|
|
|
keys = ['network', 'path', 'variant'] |
|
|
|
@ -865,6 +1106,26 @@ class JadeAPI: |
|
|
|
params = {'path': path, 'message': message} |
|
|
|
params = {'path': path, 'message': message} |
|
|
|
return self._jadeRpc('sign_message', params) |
|
|
|
return self._jadeRpc('sign_message', params) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def sign_message_file(self, message_file): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
RPC call to format and sign the given message, using the given bip32 path. |
|
|
|
|
|
|
|
A message file is provided - as produced by eg. Specter wallet. |
|
|
|
|
|
|
|
Supports RFC6979 only. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
|
|
|
|
---------- |
|
|
|
|
|
|
|
message_file : str |
|
|
|
|
|
|
|
Message file to parse and produce signature for. |
|
|
|
|
|
|
|
eg: 'signmessage m/84h/0h/0h/0/0 ascii:this is a test message' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
|
|
|
|
------- |
|
|
|
|
|
|
|
str |
|
|
|
|
|
|
|
base64-encoded RFC6979 signature |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
params = {'message_file': message_file} |
|
|
|
|
|
|
|
return self._jadeRpc('sign_message', params) |
|
|
|
|
|
|
|
|
|
|
|
def get_identity_pubkey(self, identity, curve, key_type, index=0): |
|
|
|
def get_identity_pubkey(self, identity, curve, key_type, index=0): |
|
|
|
""" |
|
|
|
""" |
|
|
|
RPC call to fetch a pubkey for the given identity (slip13/slip17). |
|
|
|
RPC call to fetch a pubkey for the given identity (slip13/slip17). |
|
|
|
@ -960,18 +1221,28 @@ class JadeAPI: |
|
|
|
params = {'identity': identity, 'curve': curve, 'index': index, 'challenge': challenge} |
|
|
|
params = {'identity': identity, 'curve': curve, 'index': index, 'challenge': challenge} |
|
|
|
return self._jadeRpc('sign_identity', params) |
|
|
|
return self._jadeRpc('sign_identity', params) |
|
|
|
|
|
|
|
|
|
|
|
def get_master_blinding_key(self): |
|
|
|
def get_master_blinding_key(self, only_if_silent=False): |
|
|
|
""" |
|
|
|
""" |
|
|
|
RPC call to fetch the master (SLIP-077) blinding key for the hw signer. |
|
|
|
RPC call to fetch the master (SLIP-077) blinding key for the hw signer. |
|
|
|
|
|
|
|
May block temporarily to request the user's permission to export. Passing 'only_if_silent' |
|
|
|
|
|
|
|
causes the call to return the 'denied' error if it would normally ask the user. |
|
|
|
NOTE: the master blinding key of any registered multisig wallets can be obtained from |
|
|
|
NOTE: the master blinding key of any registered multisig wallets can be obtained from |
|
|
|
the result of `get_registered_multisigs()`. |
|
|
|
the result of `get_registered_multisigs()`. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
|
|
|
|
---------- |
|
|
|
|
|
|
|
only_if_silent : boolean, optional |
|
|
|
|
|
|
|
If True Jade will return the denied error if it would normally ask the user's permission |
|
|
|
|
|
|
|
to export the master blinding key. Passing False (or letting default) may block while |
|
|
|
|
|
|
|
asking the user to confirm the export on Jade. |
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
Returns |
|
|
|
------- |
|
|
|
------- |
|
|
|
32-bytes |
|
|
|
32-bytes |
|
|
|
SLIP-077 master blinding key |
|
|
|
SLIP-077 master blinding key |
|
|
|
""" |
|
|
|
""" |
|
|
|
return self._jadeRpc('get_master_blinding_key') |
|
|
|
params = {'only_if_silent': only_if_silent} |
|
|
|
|
|
|
|
return self._jadeRpc('get_master_blinding_key', params) |
|
|
|
|
|
|
|
|
|
|
|
def get_blinding_key(self, script, multisig_name=None): |
|
|
|
def get_blinding_key(self, script, multisig_name=None): |
|
|
|
""" |
|
|
|
""" |
|
|
|
@ -1034,26 +1305,22 @@ class JadeAPI: |
|
|
|
|
|
|
|
|
|
|
|
def get_blinding_factor(self, hash_prevouts, output_index, bftype, multisig_name=None): |
|
|
|
def get_blinding_factor(self, hash_prevouts, output_index, bftype, multisig_name=None): |
|
|
|
""" |
|
|
|
""" |
|
|
|
RPC call to get a deterministic "trusted" blinding factor to blind an output. |
|
|
|
RPC call to get deterministic blinding factors to blind an output. |
|
|
|
Normally the blinding factors are generated and returned in the `get_commitments` call, |
|
|
|
Predicated on the host calculating the 'hash_prevouts' value correctly. |
|
|
|
but for the last output the vbf must be generated on the host, so this call allows the |
|
|
|
Can fetch abf, vbf, or both together. |
|
|
|
host to get a valid abf to compute the generator and then the "final" vbf. |
|
|
|
|
|
|
|
Nonetheless, this call is kept generic, and can also generate vbfs, hence the "bftype" |
|
|
|
|
|
|
|
parameter. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
Parameters |
|
|
|
---------- |
|
|
|
---------- |
|
|
|
|
|
|
|
|
|
|
|
hash_prevouts : 32-bytes |
|
|
|
hash_prevouts : 32-bytes |
|
|
|
This value is computed as specified in bip143. |
|
|
|
This value should be computed by the host as specified in bip143. |
|
|
|
It is verified immediately since at this point Jade doesn't have the tx in question. |
|
|
|
It is not verified by Jade, since at this point Jade does not have the tx in question. |
|
|
|
It will be checked later during `sign_liquid_tx()`. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
output_index : int |
|
|
|
output_index : int |
|
|
|
The index of the output we are trying to blind |
|
|
|
The index of the output we are trying to blind |
|
|
|
|
|
|
|
|
|
|
|
bftype : str |
|
|
|
bftype : str |
|
|
|
Can be eitehr "ASSET" or "VALUE", to generate abfs or vbfs. |
|
|
|
Can be "ASSET", "VALUE", or "ASSET_AND_VALUE", to generate abf, vbf, or both. |
|
|
|
|
|
|
|
|
|
|
|
multisig_name : str, optional |
|
|
|
multisig_name : str, optional |
|
|
|
The name of any registered multisig wallet for which to fetch the blinding factor. |
|
|
|
The name of any registered multisig wallet for which to fetch the blinding factor. |
|
|
|
@ -1061,8 +1328,9 @@ class JadeAPI: |
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
Returns |
|
|
|
------- |
|
|
|
------- |
|
|
|
32-bytes |
|
|
|
32-bytes or 64-bytes |
|
|
|
The requested blinding factor |
|
|
|
The blinding factor for "ASSET" and "VALUE" requests, or both concatenated abf|vbf |
|
|
|
|
|
|
|
ie. the first 32 bytes being abf, the second 32 bytes being vbf. |
|
|
|
""" |
|
|
|
""" |
|
|
|
params = {'hash_prevouts': hash_prevouts, |
|
|
|
params = {'hash_prevouts': hash_prevouts, |
|
|
|
'output_index': output_index, |
|
|
|
'output_index': output_index, |
|
|
|
@ -1109,7 +1377,7 @@ class JadeAPI: |
|
|
|
Returns |
|
|
|
Returns |
|
|
|
------- |
|
|
|
------- |
|
|
|
dict |
|
|
|
dict |
|
|
|
Containing the following the blinding factors and output commitments. |
|
|
|
Containing the blinding factors and output commitments. |
|
|
|
""" |
|
|
|
""" |
|
|
|
params = {'asset_id': asset_id, |
|
|
|
params = {'asset_id': asset_id, |
|
|
|
'value': value, |
|
|
|
'value': value, |
|
|
|
@ -1161,7 +1429,7 @@ class JadeAPI: |
|
|
|
host_ae_entropy_values = [] |
|
|
|
host_ae_entropy_values = [] |
|
|
|
for txinput in inputs: |
|
|
|
for txinput in inputs: |
|
|
|
# ae-protocol - do not send the host entropy immediately |
|
|
|
# ae-protocol - do not send the host entropy immediately |
|
|
|
txinput = txinput.copy() # shallow copy |
|
|
|
txinput = txinput.copy() if txinput else {} # shallow copy |
|
|
|
host_ae_entropy_values.append(txinput.pop('ae_host_entropy', None)) |
|
|
|
host_ae_entropy_values.append(txinput.pop('ae_host_entropy', None)) |
|
|
|
|
|
|
|
|
|
|
|
base_id += 1 |
|
|
|
base_id += 1 |
|
|
|
@ -1193,6 +1461,9 @@ class JadeAPI: |
|
|
|
# Send all n inputs |
|
|
|
# Send all n inputs |
|
|
|
requests = [] |
|
|
|
requests = [] |
|
|
|
for txinput in inputs: |
|
|
|
for txinput in inputs: |
|
|
|
|
|
|
|
if txinput is None: |
|
|
|
|
|
|
|
txinput = {} |
|
|
|
|
|
|
|
|
|
|
|
base_id += 1 |
|
|
|
base_id += 1 |
|
|
|
msg_id = str(base_id) |
|
|
|
msg_id = str(base_id) |
|
|
|
request = self.jade.build_request(msg_id, 'tx_input', txinput) |
|
|
|
request = self.jade.build_request(msg_id, 'tx_input', txinput) |
|
|
|
@ -1212,31 +1483,44 @@ class JadeAPI: |
|
|
|
return signatures |
|
|
|
return signatures |
|
|
|
|
|
|
|
|
|
|
|
def sign_liquid_tx(self, network, txn, inputs, commitments, change, use_ae_signatures=False, |
|
|
|
def sign_liquid_tx(self, network, txn, inputs, commitments, change, use_ae_signatures=False, |
|
|
|
asset_info=None): |
|
|
|
asset_info=None, additional_info=None): |
|
|
|
""" |
|
|
|
""" |
|
|
|
RPC call to sign a liquid transaction. |
|
|
|
RPC call to sign a liquid transaction. |
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
Parameters |
|
|
|
---------- |
|
|
|
---------- |
|
|
|
network : str |
|
|
|
network : str |
|
|
|
Network to which the address should apply - eg. 'liquid', 'liquid-testnet', etc. |
|
|
|
Network to which the txn should apply - eg. 'liquid', 'liquid-testnet', etc. |
|
|
|
|
|
|
|
|
|
|
|
txn : bytes |
|
|
|
txn : bytes |
|
|
|
The transaction to sign |
|
|
|
The transaction to sign |
|
|
|
|
|
|
|
|
|
|
|
inputs : [dict] |
|
|
|
inputs : [dict] |
|
|
|
The tx inputs. Should contain keys: |
|
|
|
The tx inputs. |
|
|
|
|
|
|
|
If signing this input, should contain keys: |
|
|
|
is_witness, bool - whether this is a segwit input |
|
|
|
is_witness, bool - whether this is a segwit input |
|
|
|
value_commitment, 33-bytes - The value commitment of ths input |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
These are only required if signing this input: |
|
|
|
|
|
|
|
script, bytes- the redeem script |
|
|
|
script, bytes- the redeem script |
|
|
|
path, [int] - the bip32 path to sign with |
|
|
|
path, [int] - the bip32 path to sign with |
|
|
|
|
|
|
|
value_commitment, 33-bytes - The value commitment of ths input |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This is optional if signing this input: |
|
|
|
|
|
|
|
sighash, int - The sighash to use, defaults to 0x01 (SIGHASH_ALL) |
|
|
|
|
|
|
|
|
|
|
|
These are only required for Anti-Exfil signatures: |
|
|
|
These are only required for Anti-Exfil signatures: |
|
|
|
ae_host_commitment, 32-bytes - The host-commitment for Anti-Exfil signatures |
|
|
|
ae_host_commitment, 32-bytes - The host-commitment for Anti-Exfil signatures |
|
|
|
ae_host_entropy, 32-bytes - The host-entropy for Anti-Exfil signatures |
|
|
|
ae_host_entropy, 32-bytes - The host-entropy for Anti-Exfil signatures |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
These are only required for advanced transactions, eg. swaps, and only when the |
|
|
|
|
|
|
|
inputs need unblinding. |
|
|
|
|
|
|
|
Not needed for vanilla send-payment/redeposit etc: |
|
|
|
|
|
|
|
abf, 32-bytes - asset blinding factor |
|
|
|
|
|
|
|
asset_id, 32-bytes - the unblinded asset-id |
|
|
|
|
|
|
|
asset_generator, 33-bytes - the (blinded) asset-generator |
|
|
|
|
|
|
|
vbf, 32-bytes - the value blinding factor |
|
|
|
|
|
|
|
value, int - the unblinded sats value of the input |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
If not signing this input a null or an empty dict can be passed. |
|
|
|
|
|
|
|
|
|
|
|
commitments : [dict] |
|
|
|
commitments : [dict] |
|
|
|
An array sized for the number of outputs. |
|
|
|
An array sized for the number of outputs. |
|
|
|
Unblinded outputs should have a 'null' placeholder element. |
|
|
|
Unblinded outputs should have a 'null' placeholder element. |
|
|
|
@ -1246,16 +1530,18 @@ class JadeAPI: |
|
|
|
|
|
|
|
|
|
|
|
change : [dict] |
|
|
|
change : [dict] |
|
|
|
An array sized for the number of outputs. |
|
|
|
An array sized for the number of outputs. |
|
|
|
Outputs which are not change should have a 'null' placeholder element. |
|
|
|
Outputs which are not to this wallet should have a 'null' placeholder element. |
|
|
|
Change elements with data will be automatically verified by Jade, and not by the user. |
|
|
|
The output scripts for the elements with data will be verified by Jade. |
|
|
|
Populated elements should contain sufficient data to generate the change address. |
|
|
|
Unless the element also contains 'is_change': False, these outputs will automatically |
|
|
|
|
|
|
|
be approved and not be verified by the user. |
|
|
|
|
|
|
|
Populated elements should contain sufficient data to generate the wallet address. |
|
|
|
See `get_receive_address()` |
|
|
|
See `get_receive_address()` |
|
|
|
|
|
|
|
|
|
|
|
use_ae_signatures : bool, optional |
|
|
|
use_ae_signatures : bool, optional |
|
|
|
Whether to use the anti-exfil protocol to generate the signatures. |
|
|
|
Whether to use the anti-exfil protocol to generate the signatures. |
|
|
|
Defaults to False. |
|
|
|
Defaults to False. |
|
|
|
|
|
|
|
|
|
|
|
asset_info : [dict] |
|
|
|
asset_info : [dict], optional |
|
|
|
Any asset-registry data relevant to the assets being transacted, such that Jade can |
|
|
|
Any asset-registry data relevant to the assets being transacted, such that Jade can |
|
|
|
display a meaningful name, issuer, ticker etc. rather than just asset-id. |
|
|
|
display a meaningful name, issuer, ticker etc. rather than just asset-id. |
|
|
|
At the very least must contain 'asset_id', 'contract' and 'issuance_prevout' items, |
|
|
|
At the very least must contain 'asset_id', 'contract' and 'issuance_prevout' items, |
|
|
|
@ -1263,6 +1549,17 @@ class JadeAPI: |
|
|
|
not required. |
|
|
|
not required. |
|
|
|
Defaults to None. |
|
|
|
Defaults to None. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
additional_info: dict, optional |
|
|
|
|
|
|
|
Extra data about the transaction. Only required for advanced transactions, eg. swaps. |
|
|
|
|
|
|
|
Not needed for vanilla send-payment/redeposit etc: |
|
|
|
|
|
|
|
tx_type, str: 'swap' indicates the tx represents an asset-swap proposal or transaction. |
|
|
|
|
|
|
|
wallet_input_summary, dict: a list of entries containing 'asset_id' (32-bytes) and |
|
|
|
|
|
|
|
'satoshi' (int) showing net movement of assets out of the wallet (ie. sum of wallet |
|
|
|
|
|
|
|
inputs per asset, minus any change outputs). |
|
|
|
|
|
|
|
wallet_output_summary, dict: a list of entries containing 'asset_id' (32-bytes) and |
|
|
|
|
|
|
|
'satoshi' (int) showing net movement of assets into the wallet (ie. sum of wallet |
|
|
|
|
|
|
|
outputs per asset, excluding any change outputs). |
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
Returns |
|
|
|
------- |
|
|
|
------- |
|
|
|
1. if use_ae_signatures is False |
|
|
|
1. if use_ae_signatures is False |
|
|
|
@ -1286,7 +1583,8 @@ class JadeAPI: |
|
|
|
'trusted_commitments': commitments, |
|
|
|
'trusted_commitments': commitments, |
|
|
|
'use_ae_signatures': use_ae_signatures, |
|
|
|
'use_ae_signatures': use_ae_signatures, |
|
|
|
'change': change, |
|
|
|
'change': change, |
|
|
|
'asset_info': asset_info} |
|
|
|
'asset_info': asset_info, |
|
|
|
|
|
|
|
'additional_info': additional_info} |
|
|
|
|
|
|
|
|
|
|
|
reply = self._jadeRpc('sign_liquid_tx', params, str(base_id)) |
|
|
|
reply = self._jadeRpc('sign_liquid_tx', params, str(base_id)) |
|
|
|
assert reply |
|
|
|
assert reply |
|
|
|
@ -1301,23 +1599,25 @@ class JadeAPI: |
|
|
|
Parameters |
|
|
|
Parameters |
|
|
|
---------- |
|
|
|
---------- |
|
|
|
network : str |
|
|
|
network : str |
|
|
|
Network to which the address should apply - eg. 'mainnet', 'testnet', etc. |
|
|
|
Network to which the txn should apply - eg. 'mainnet', 'testnet', etc. |
|
|
|
|
|
|
|
|
|
|
|
txn : bytes |
|
|
|
txn : bytes |
|
|
|
The transaction to sign |
|
|
|
The transaction to sign |
|
|
|
|
|
|
|
|
|
|
|
inputs : [dict] |
|
|
|
inputs : [dict] |
|
|
|
The tx inputs. Should contain keys: |
|
|
|
The tx inputs. Should contain keys: |
|
|
|
is_witness, bool - whether this is a segwit input |
|
|
|
One of these is required: |
|
|
|
|
|
|
|
input_tx, bytes - The prior transaction which created the utxo of this input |
|
|
|
|
|
|
|
satoshi, int - The satoshi amount of this input - can be used in place of |
|
|
|
|
|
|
|
'input_tx' for a tx with a single segwit input |
|
|
|
|
|
|
|
|
|
|
|
These are only required if signing this input: |
|
|
|
These are only required if signing this input: |
|
|
|
|
|
|
|
is_witness, bool - whether this is a segwit input |
|
|
|
script, bytes- the redeem script |
|
|
|
script, bytes- the redeem script |
|
|
|
path, [int] - the bip32 path to sign with |
|
|
|
path, [int] - the bip32 path to sign with |
|
|
|
|
|
|
|
|
|
|
|
One of these is required: |
|
|
|
This is optional if signing this input: |
|
|
|
input_tx, bytes - The prior transaction which created the utxo of this input |
|
|
|
sighash, int - The sighash to use, defaults to 0x01 (SIGHASH_ALL) |
|
|
|
satoshi, int - The satoshi amount of this input - can be used in place of |
|
|
|
|
|
|
|
'input_tx' for a tx with a single segwit input |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
These are only required for Anti-Exfil signatures: |
|
|
|
These are only required for Anti-Exfil signatures: |
|
|
|
ae_host_commitment, 32-bytes - The host-commitment for Anti-Exfil signatures |
|
|
|
ae_host_commitment, 32-bytes - The host-commitment for Anti-Exfil signatures |
|
|
|
@ -1325,9 +1625,11 @@ class JadeAPI: |
|
|
|
|
|
|
|
|
|
|
|
change : [dict] |
|
|
|
change : [dict] |
|
|
|
An array sized for the number of outputs. |
|
|
|
An array sized for the number of outputs. |
|
|
|
Outputs which are not change should have a 'null' placeholder element. |
|
|
|
Outputs which are not to this wallet should have a 'null' placeholder element. |
|
|
|
Change elements with data will be automatically verified by Jade, and not by the user. |
|
|
|
The output scripts for the elements with data will be verified by Jade. |
|
|
|
Populated elements should contain sufficient data to generate the change address. |
|
|
|
Unless the element also contains 'is_change': False, these outputs will automatically |
|
|
|
|
|
|
|
be approved and not be verified by the user. |
|
|
|
|
|
|
|
Populated elements should contain sufficient data to generate the wallet address. |
|
|
|
See `get_receive_address()` |
|
|
|
See `get_receive_address()` |
|
|
|
|
|
|
|
|
|
|
|
use_ae_signatures : bool |
|
|
|
use_ae_signatures : bool |
|
|
|
@ -1362,6 +1664,50 @@ class JadeAPI: |
|
|
|
# Send inputs and receive signatures |
|
|
|
# Send inputs and receive signatures |
|
|
|
return self._send_tx_inputs(base_id, inputs, use_ae_signatures) |
|
|
|
return self._send_tx_inputs(base_id, inputs, use_ae_signatures) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def sign_psbt(self, network, psbt): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
RPC call to sign a passed psbt as required |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters |
|
|
|
|
|
|
|
---------- |
|
|
|
|
|
|
|
network : str |
|
|
|
|
|
|
|
Network to which the txn should apply - eg. 'mainnet', 'testnet', etc. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
psbt : bytes |
|
|
|
|
|
|
|
The psbt formatted as bytes |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns |
|
|
|
|
|
|
|
------- |
|
|
|
|
|
|
|
bytes |
|
|
|
|
|
|
|
The psbt, updated with any signatures required from the hw signer |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
# Send PSBT message |
|
|
|
|
|
|
|
params = {'network': network, 'psbt': psbt} |
|
|
|
|
|
|
|
msgid = str(random.randint(100000, 999999)) |
|
|
|
|
|
|
|
request = self.jade.build_request(msgid, 'sign_psbt', params) |
|
|
|
|
|
|
|
self.jade.write_request(request) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Read replies until we have them all, collate data and return. |
|
|
|
|
|
|
|
# NOTE: we send 'get_extended_data' messages to request more 'chunks' of the reply data. |
|
|
|
|
|
|
|
psbt_out = bytearray() |
|
|
|
|
|
|
|
while True: |
|
|
|
|
|
|
|
reply = self.jade.read_response() |
|
|
|
|
|
|
|
self.jade.validate_reply(request, reply) |
|
|
|
|
|
|
|
psbt_out.extend(self._get_result_or_raise_error(reply)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if 'seqnum' not in reply or reply['seqnum'] == reply['seqlen']: |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
newid = str(random.randint(100000, 999999)) |
|
|
|
|
|
|
|
params = {'origid': msgid, |
|
|
|
|
|
|
|
'orig': 'sign_psbt', |
|
|
|
|
|
|
|
'seqnum': reply['seqnum'] + 1, |
|
|
|
|
|
|
|
'seqlen': reply['seqlen']} |
|
|
|
|
|
|
|
request = self.jade.build_request(newid, 'get_extended_data', params) |
|
|
|
|
|
|
|
self.jade.write_request(request) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return psbt_out |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class JadeInterface: |
|
|
|
class JadeInterface: |
|
|
|
""" |
|
|
|
""" |
|
|
|
@ -1394,9 +1740,9 @@ class JadeInterface: |
|
|
|
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc, tb): |
|
|
|
def __exit__(self, exc_type, exc, tb): |
|
|
|
if (exc_type): |
|
|
|
if (exc_type): |
|
|
|
logger.error("Exception causing JadeInterface context exit.") |
|
|
|
logger.info("Exception causing JadeInterface context exit.") |
|
|
|
logger.error(exc_type) |
|
|
|
logger.info(exc_type) |
|
|
|
logger.error(exc) |
|
|
|
logger.info(exc) |
|
|
|
traceback.print_tb(tb) |
|
|
|
traceback.print_tb(tb) |
|
|
|
self.disconnect(exc_type is not None) |
|
|
|
self.disconnect(exc_type is not None) |
|
|
|
|
|
|
|
|
|
|
|
@ -1422,14 +1768,14 @@ class JadeInterface: |
|
|
|
Returns |
|
|
|
Returns |
|
|
|
------- |
|
|
|
------- |
|
|
|
JadeInterface |
|
|
|
JadeInterface |
|
|
|
Interface object configured to use given serial parameters. |
|
|
|
Inerface object configured to use given serial parameters. |
|
|
|
NOTE: the instance has not yet tried to contact the hw |
|
|
|
NOTE: the instance has not yet tried to contact the hw |
|
|
|
- caller must call 'connect()' before trying to use the Jade. |
|
|
|
- caller must call 'connect()' before trying to use the Jade. |
|
|
|
""" |
|
|
|
""" |
|
|
|
if device and JadeTCPImpl.isSupportedDevice(device): |
|
|
|
if device and JadeTCPImpl.isSupportedDevice(device): |
|
|
|
impl = JadeTCPImpl(device) |
|
|
|
impl = JadeTCPImpl(device, timeout or DEFAULT_SERIAL_TIMEOUT) |
|
|
|
else: |
|
|
|
else: |
|
|
|
impl = JadeSerialImpl(device or DEFAULT_SERIAL_DEVICE, |
|
|
|
impl = JadeSerialImpl(device, |
|
|
|
baud or DEFAULT_BAUD_RATE, |
|
|
|
baud or DEFAULT_BAUD_RATE, |
|
|
|
timeout or DEFAULT_SERIAL_TIMEOUT) |
|
|
|
timeout or DEFAULT_SERIAL_TIMEOUT) |
|
|
|
return JadeInterface(impl) |
|
|
|
return JadeInterface(impl) |
|
|
|
@ -1464,7 +1810,7 @@ class JadeInterface: |
|
|
|
Returns |
|
|
|
Returns |
|
|
|
------- |
|
|
|
------- |
|
|
|
JadeInterface |
|
|
|
JadeInterface |
|
|
|
Interface object configured to use given BLE parameters. |
|
|
|
Inerface object configured to use given BLE parameters. |
|
|
|
NOTE: the instance has not yet tried to contact the hw |
|
|
|
NOTE: the instance has not yet tried to contact the hw |
|
|
|
- caller must call 'connect()' before trying to use the Jade. |
|
|
|
- caller must call 'connect()' before trying to use the Jade. |
|
|
|
|
|
|
|
|
|
|
|
@ -1510,7 +1856,7 @@ class JadeInterface: |
|
|
|
Log any/all outstanding messages/data. |
|
|
|
Log any/all outstanding messages/data. |
|
|
|
NOTE: can run indefinitely if data is arriving constantly. |
|
|
|
NOTE: can run indefinitely if data is arriving constantly. |
|
|
|
""" |
|
|
|
""" |
|
|
|
logger.warn("Draining interface...") |
|
|
|
logger.warning("Draining interface...") |
|
|
|
drained = bytearray() |
|
|
|
drained = bytearray() |
|
|
|
finished = False |
|
|
|
finished = False |
|
|
|
|
|
|
|
|
|
|
|
@ -1521,14 +1867,14 @@ class JadeInterface: |
|
|
|
|
|
|
|
|
|
|
|
if finished or byte_ == b'\n' or len(drained) > 256: |
|
|
|
if finished or byte_ == b'\n' or len(drained) > 256: |
|
|
|
try: |
|
|
|
try: |
|
|
|
device_logger.warn(drained.decode('utf-8')) |
|
|
|
device_logger.warning(drained.decode('utf-8')) |
|
|
|
except Exception as e: |
|
|
|
except Exception as e: |
|
|
|
# Dump the bytes raw and as hex if decoding as utf-8 failed |
|
|
|
# Dump the bytes raw and as hex if decoding as utf-8 failed |
|
|
|
device_logger.warn("Raw:") |
|
|
|
device_logger.warning("Raw:") |
|
|
|
device_logger.warn(drained) |
|
|
|
device_logger.warning(drained) |
|
|
|
device_logger.warn("----") |
|
|
|
device_logger.warning("----") |
|
|
|
device_logger.warn("Hex dump:") |
|
|
|
device_logger.warning("Hex dump:") |
|
|
|
device_logger.warn(drained.hex()) |
|
|
|
device_logger.warning(drained.hex()) |
|
|
|
|
|
|
|
|
|
|
|
# Clear and loop to continue collecting |
|
|
|
# Clear and loop to continue collecting |
|
|
|
drained.clear() |
|
|
|
drained.clear() |
|
|
|
@ -1662,7 +2008,7 @@ class JadeInterface: |
|
|
|
response = message['log'].decode("utf-8") |
|
|
|
response = message['log'].decode("utf-8") |
|
|
|
log_methods = { |
|
|
|
log_methods = { |
|
|
|
'E': device_logger.error, |
|
|
|
'E': device_logger.error, |
|
|
|
'W': device_logger.warn, |
|
|
|
'W': device_logger.warning, |
|
|
|
'I': device_logger.info, |
|
|
|
'I': device_logger.info, |
|
|
|
'D': device_logger.debug, |
|
|
|
'D': device_logger.debug, |
|
|
|
'V': device_logger.debug, |
|
|
|
'V': device_logger.debug, |
|
|
|
@ -1718,7 +2064,7 @@ class JadeInterface: |
|
|
|
def make_rpc_call(self, request, long_timeout=False): |
|
|
|
def make_rpc_call(self, request, long_timeout=False): |
|
|
|
""" |
|
|
|
""" |
|
|
|
Method to send a request over the underlying interface, and await a response. |
|
|
|
Method to send a request over the underlying interface, and await a response. |
|
|
|
The request is minimally validated before it is sent, and the response is similarly |
|
|
|
The request is minimally validated before it is sent, and the response is simialrly |
|
|
|
validated before being returned. |
|
|
|
validated before being returned. |
|
|
|
Any read-timeout is respected unless 'long_timeout' is passed, in which case the call |
|
|
|
Any read-timeout is respected unless 'long_timeout' is passed, in which case the call |
|
|
|
blocks indefinitely awaiting a response. |
|
|
|
blocks indefinitely awaiting a response. |
|
|
|
|