|
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