|
52 | 52 | parse_multisig
|
53 | 53 | )
|
54 | 54 |
|
| 55 | +import base64 |
55 | 56 | import logging
|
56 | 57 | import semver
|
57 | 58 | import os
|
@@ -90,6 +91,7 @@ def func(*args: Any, **kwargs: Any) -> Any:
|
90 | 91 | # This class extends the HardwareWalletClient for Blockstream Jade specific things
|
91 | 92 | class JadeClient(HardwareWalletClient):
|
92 | 93 | MIN_SUPPORTED_FW_VERSION = semver.VersionInfo(0, 1, 32)
|
| 94 | + PSBT_SUPPORTED_FW_VERSION = semver.VersionInfo(0, 1, 47) |
93 | 95 |
|
94 | 96 | NETWORKS = {Chain.MAIN: 'mainnet',
|
95 | 97 | Chain.TEST: 'testnet',
|
@@ -131,12 +133,12 @@ def __init__(self, path: str, password: Optional[str] = None, expert: bool = Fal
|
131 | 133 | self.jade.connect()
|
132 | 134 |
|
133 | 135 | verinfo = self.jade.get_version_info()
|
| 136 | + self.fw_version = semver.parse_version_info(verinfo['JADE_VERSION']) |
134 | 137 | uninitialized = verinfo['JADE_STATE'] not in ['READY', 'TEMP']
|
135 | 138 |
|
136 | 139 | # Check minimum supported firmware version (ignore candidate/build parts)
|
137 |
| - fw_version = semver.parse_version_info(verinfo['JADE_VERSION']) |
138 |
| - if self.MIN_SUPPORTED_FW_VERSION > fw_version.finalize_version(): |
139 |
| - raise DeviceNotReadyError(f'Jade fw version: {fw_version} - minimum required version: {self.MIN_SUPPORTED_FW_VERSION}. ' |
| 140 | + if self.MIN_SUPPORTED_FW_VERSION > self.fw_version.finalize_version(): |
| 141 | + raise DeviceNotReadyError(f'Jade fw version: {self.fw_version} - minimum required version: {self.MIN_SUPPORTED_FW_VERSION}. ' |
140 | 142 | 'Please update using a Blockstream Green companion app')
|
141 | 143 | if path == SIMULATOR_PATH:
|
142 | 144 | if uninitialized:
|
@@ -165,10 +167,9 @@ def get_pubkey_at_path(self, bip32_path: str) -> ExtendedKey:
|
165 | 167 | ext_key = ExtendedKey.deserialize(xpub)
|
166 | 168 | return ext_key
|
167 | 169 |
|
168 |
| - # Walk the PSBT looking for inputs we can sign. Push any signatures into the |
169 |
| - # 'partial_sigs' map in the input, and return the updated PSBT. |
170 |
| - @jade_exception |
171 |
| - def sign_tx(self, tx: PSBT) -> PSBT: |
| 170 | + # Old firmware does not have native PSBT handling - walk the PSBT looking for inputs we can sign. |
| 171 | + # Push any signatures into the 'partial_sigs' map in the input, and return the updated PSBT. |
| 172 | + def legacy_sign_tx(self, tx: PSBT) -> PSBT: |
172 | 173 | """
|
173 | 174 | Sign a transaction with the Blockstream Jade.
|
174 | 175 | """
|
@@ -366,6 +367,29 @@ def _split_at_last_hardened_element(path: Sequence[int]) -> Tuple[Sequence[int],
|
366 | 367 | # Return the updated psbt
|
367 | 368 | return tx
|
368 | 369 |
|
| 370 | + # Sign tx PSBT - newer Jade firmware supports native PSBT signing, but old firmwares require |
| 371 | + # mapping to the legacy 'sign_tx' structures. |
| 372 | + @jade_exception |
| 373 | + def sign_tx(self, tx: PSBT) -> PSBT: |
| 374 | + """ |
| 375 | + Sign a transaction with the Blockstream Jade. |
| 376 | + """ |
| 377 | + # Old firmware does not have native PSBT handling - use legacy method |
| 378 | + if self.PSBT_SUPPORTED_FW_VERSION > self.fw_version.finalize_version(): |
| 379 | + return self.legacy_sign_tx(tx) |
| 380 | + |
| 381 | + # Firmware 0.1.47 (March 2023) and later support native PSBT signing |
| 382 | + psbt_b64 = tx.serialize() |
| 383 | + psbt_bytes = base64.b64decode(psbt_b64.strip()) |
| 384 | + |
| 385 | + # NOTE: sign_psbt() does not use AE signatures, so sticks with default (rfc6979) |
| 386 | + psbt_bytes = self.jade.sign_psbt(self._network(), psbt_bytes) |
| 387 | + psbt_b64 = base64.b64encode(psbt_bytes).decode() |
| 388 | + |
| 389 | + psbt_signed = PSBT() |
| 390 | + psbt_signed.deserialize(psbt_b64) |
| 391 | + return psbt_signed |
| 392 | + |
369 | 393 | # Sign message, confirmed on device
|
370 | 394 | @jade_exception
|
371 | 395 | def sign_message(self, message: Union[str, bytes], bip32_path: str) -> str:
|
|
0 commit comments