diff --git a/core/src/apps/bitcoin/sign_tx/helpers.py b/core/src/apps/bitcoin/sign_tx/helpers.py index 3c3e8107d0..36b8ece6ff 100644 --- a/core/src/apps/bitcoin/sign_tx/helpers.py +++ b/core/src/apps/bitcoin/sign_tx/helpers.py @@ -402,6 +402,15 @@ def sanitize_sign_tx(tx: SignTx, coin: CoinInfo) -> SignTx: if tx.branch_id is not None: raise wire.DataError("Branch ID not enabled on this coin.") + if tx.orchard_params is not None: + if not utils.ZCASH_SHIELDED: + raise wire.DataError("Shielded transaction disabled.") + elif tx.orchard_params.inputs_count + tx.orchard_params.outputs_count == 0: + raise wire.DataError("Orchard bundle must not be empty.") + elif tx.orchard_params.inputs_count > 0 and tx.inputs_count > 0: + raise wire.DataError( + "Spending transparent and Orchard inputs simultaneously is not supported." + ) return tx diff --git a/core/src/apps/zcash/orchard/signer.py b/core/src/apps/zcash/orchard/signer.py index 035717e913..37b33016d1 100644 --- a/core/src/apps/zcash/orchard/signer.py +++ b/core/src/apps/zcash/orchard/signer.py @@ -35,21 +35,6 @@ FLAGS = const(0b0000_0011) # spends enbled and output enabled MAX_SILENT_ORCHARD_INPUTS = const(8) -def skip_if_empty(func): - """ - A function decorated by this will not be evaluated, - if the Orchard bundle is impty. - """ - - async def wrapper(self): - if self.actions_count == 0: - return - else: - await func(self) - - return wrapper - - class OrchardSigner: def __init__( self, @@ -59,26 +44,13 @@ class OrchardSigner: coin: CoinInfo, tx_req: TxRequest, ) -> None: - assert tx_req.serialized is not None # typing params = tx_info.tx.orchard_params - if params is None: - self.inputs_count = 0 - self.outputs_count = 0 - else: - self.inputs_count = params.inputs_count - self.outputs_count = params.outputs_count - - if self.inputs_count + self.outputs_count > 0: - self.actions_count = max( - 2, # minimal required amount of actions - self.inputs_count, - self.outputs_count, - ) - else: - self.actions_count = 0 - - if self.actions_count == 0: - return # no need to initialize other attributes + assert params is not None # checked in sanitize_sign_tx + self.actions_count = max( + 2, # minimal required amount of actions + self.inputs_count, + self.outputs_count, + ) self.tx_info = tx_info self.keychain = OrchardKeychain.from_seed_and_coin(seed, coin) @@ -87,7 +59,6 @@ class OrchardSigner: self.tx_req = tx_req assert isinstance(tx_info.sig_hasher, ZcashHasher) self.sig_hasher: ZcashHasher = tx_info.sig_hasher - assert params is not None self.anchor = params.anchor self.key_node = self.keychain.derive(params.address_n) @@ -99,7 +70,6 @@ class OrchardSigner: self.rng = None - @skip_if_empty async def process_inputs(self) -> None: await self.check_orchard_inputs_count() for i in range(self.inputs_count): @@ -111,7 +81,6 @@ class OrchardSigner: if self.inputs_count > MAX_SILENT_ORCHARD_INPUTS: yield ConfirmOrchardInputsCountOverThreshold(self.inputs_count) - @skip_if_empty async def approve_outputs(self) -> None: for i in range(self.outputs_count): txo = await self.get_output(i) @@ -121,7 +90,6 @@ class OrchardSigner: else: await self.approver.add_orchard_external_output(txo) - @skip_if_empty async def compute_digest(self) -> None: # derive shielding seed shielding_seed = self.derive_shielding_seed() @@ -233,7 +201,6 @@ class OrchardSigner: ovk = fvk.outgoing_viewing_key() return builder.OutputInfo(ovk, address, txo.amount, txo.memo) - @skip_if_empty @watch_gc_async async def sign_inputs(self) -> None: sighash = self.sig_hasher.signature_digest() diff --git a/core/src/apps/zcash/signer.py b/core/src/apps/zcash/signer.py index 0d4a3ef7f8..a0903892df 100644 --- a/core/src/apps/zcash/signer.py +++ b/core/src/apps/zcash/signer.py @@ -55,7 +55,7 @@ class Zcash(Bitcoinlike): super().__init__(tx, keychain, coin, approver) - if ZCASH_SHIELDED: + if ZCASH_SHIELDED and tx.orchard_params is not None: self.orchard = OrchardSigner( self.tx_info, keychain.seed, @@ -63,10 +63,8 @@ class Zcash(Bitcoinlike): coin, self.tx_req, ) - if self.orchard.inputs_count > 0 and tx.inputs_count > 0: - raise DataError( - "Cannot spending transparent and Orchard inputs simultaneously is not supported." - ) + else: + self.orchard = None def create_sig_hasher(self, tx: SignTx | PrevTx) -> ZcashHasher: return ZcashHasher(tx) @@ -78,12 +76,12 @@ class Zcash(Bitcoinlike): async def step1_process_inputs(self): await super().step1_process_inputs() - if ZCASH_SHIELDED: + if ZCASH_SHIELDED and self.orchard is not None: await self.orchard.process_inputs() async def step2_approve_outputs(self): await super().step2_approve_outputs() - if ZCASH_SHIELDED: + if ZCASH_SHIELDED and self.orchard is not None: await self.orchard.approve_outputs() async def step3_verify_inputs(self) -> None: @@ -97,7 +95,7 @@ class Zcash(Bitcoinlike): self.taproot_only = False # turn off taproot behavior async def step4_serialize_inputs(self): - if ZCASH_SHIELDED: + if ZCASH_SHIELDED and self.orchard is not None: # shield actions first to get a sighash await self.orchard.compute_digest() await super().step4_serialize_inputs() @@ -111,14 +109,14 @@ class Zcash(Bitcoinlike): write_compact_size(self.serialized_tx, 0) # nOutputsSapling # nActionsOrchard - if ZCASH_SHIELDED: + if ZCASH_SHIELDED and self.orchard is not None:: write_compact_size(self.serialized_tx, self.orchard.actions_count) else: write_compact_size(self.serialized_tx, 0) async def step6_sign_segwit_inputs(self): # transparent inputs were signed in step 4 - if ZCASH_SHIELDED: + if ZCASH_SHIELDED and self.orchard is not None: await self.orchard.sign_inputs() async def sign_nonsegwit_input(self, i_sign: int) -> None: @@ -186,5 +184,5 @@ class Zcash(Bitcoinlike): def set_serialized_signature(self, i: int, signature: bytes) -> None: super().set_serialized_signature(i, signature) assert self.tx_req.serialized is not None - if ZCASH_SHIELDED: + if ZCASH_SHIELDED and self.orchard is not None: self.tx_req.serialized.signature_type = ZcashSignatureType.TRANSPARENT