mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-22 20:42:03 +00:00
fixup! feat(core): add Zcash shielded transactions
This commit is contained in:
parent
8bddb42785
commit
8384dc6e58
@ -402,6 +402,15 @@ def sanitize_sign_tx(tx: SignTx, coin: CoinInfo) -> SignTx:
|
|||||||
if tx.branch_id is not None:
|
if tx.branch_id is not None:
|
||||||
raise wire.DataError("Branch ID not enabled on this coin.")
|
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
|
return tx
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,21 +35,6 @@ FLAGS = const(0b0000_0011) # spends enbled and output enabled
|
|||||||
MAX_SILENT_ORCHARD_INPUTS = const(8)
|
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:
|
class OrchardSigner:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -59,26 +44,13 @@ class OrchardSigner:
|
|||||||
coin: CoinInfo,
|
coin: CoinInfo,
|
||||||
tx_req: TxRequest,
|
tx_req: TxRequest,
|
||||||
) -> None:
|
) -> None:
|
||||||
assert tx_req.serialized is not None # typing
|
|
||||||
params = tx_info.tx.orchard_params
|
params = tx_info.tx.orchard_params
|
||||||
if params is None:
|
assert params is not None # checked in sanitize_sign_tx
|
||||||
self.inputs_count = 0
|
self.actions_count = max(
|
||||||
self.outputs_count = 0
|
2, # minimal required amount of actions
|
||||||
else:
|
self.inputs_count,
|
||||||
self.inputs_count = params.inputs_count
|
self.outputs_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
|
|
||||||
|
|
||||||
self.tx_info = tx_info
|
self.tx_info = tx_info
|
||||||
self.keychain = OrchardKeychain.from_seed_and_coin(seed, coin)
|
self.keychain = OrchardKeychain.from_seed_and_coin(seed, coin)
|
||||||
@ -87,7 +59,6 @@ class OrchardSigner:
|
|||||||
self.tx_req = tx_req
|
self.tx_req = tx_req
|
||||||
assert isinstance(tx_info.sig_hasher, ZcashHasher)
|
assert isinstance(tx_info.sig_hasher, ZcashHasher)
|
||||||
self.sig_hasher: ZcashHasher = tx_info.sig_hasher
|
self.sig_hasher: ZcashHasher = tx_info.sig_hasher
|
||||||
assert params is not None
|
|
||||||
self.anchor = params.anchor
|
self.anchor = params.anchor
|
||||||
self.key_node = self.keychain.derive(params.address_n)
|
self.key_node = self.keychain.derive(params.address_n)
|
||||||
|
|
||||||
@ -99,7 +70,6 @@ class OrchardSigner:
|
|||||||
|
|
||||||
self.rng = None
|
self.rng = None
|
||||||
|
|
||||||
@skip_if_empty
|
|
||||||
async def process_inputs(self) -> None:
|
async def process_inputs(self) -> None:
|
||||||
await self.check_orchard_inputs_count()
|
await self.check_orchard_inputs_count()
|
||||||
for i in range(self.inputs_count):
|
for i in range(self.inputs_count):
|
||||||
@ -111,7 +81,6 @@ class OrchardSigner:
|
|||||||
if self.inputs_count > MAX_SILENT_ORCHARD_INPUTS:
|
if self.inputs_count > MAX_SILENT_ORCHARD_INPUTS:
|
||||||
yield ConfirmOrchardInputsCountOverThreshold(self.inputs_count)
|
yield ConfirmOrchardInputsCountOverThreshold(self.inputs_count)
|
||||||
|
|
||||||
@skip_if_empty
|
|
||||||
async def approve_outputs(self) -> None:
|
async def approve_outputs(self) -> None:
|
||||||
for i in range(self.outputs_count):
|
for i in range(self.outputs_count):
|
||||||
txo = await self.get_output(i)
|
txo = await self.get_output(i)
|
||||||
@ -121,7 +90,6 @@ class OrchardSigner:
|
|||||||
else:
|
else:
|
||||||
await self.approver.add_orchard_external_output(txo)
|
await self.approver.add_orchard_external_output(txo)
|
||||||
|
|
||||||
@skip_if_empty
|
|
||||||
async def compute_digest(self) -> None:
|
async def compute_digest(self) -> None:
|
||||||
# derive shielding seed
|
# derive shielding seed
|
||||||
shielding_seed = self.derive_shielding_seed()
|
shielding_seed = self.derive_shielding_seed()
|
||||||
@ -233,7 +201,6 @@ class OrchardSigner:
|
|||||||
ovk = fvk.outgoing_viewing_key()
|
ovk = fvk.outgoing_viewing_key()
|
||||||
return builder.OutputInfo(ovk, address, txo.amount, txo.memo)
|
return builder.OutputInfo(ovk, address, txo.amount, txo.memo)
|
||||||
|
|
||||||
@skip_if_empty
|
|
||||||
@watch_gc_async
|
@watch_gc_async
|
||||||
async def sign_inputs(self) -> None:
|
async def sign_inputs(self) -> None:
|
||||||
sighash = self.sig_hasher.signature_digest()
|
sighash = self.sig_hasher.signature_digest()
|
||||||
|
@ -55,7 +55,7 @@ class Zcash(Bitcoinlike):
|
|||||||
|
|
||||||
super().__init__(tx, keychain, coin, approver)
|
super().__init__(tx, keychain, coin, approver)
|
||||||
|
|
||||||
if ZCASH_SHIELDED:
|
if ZCASH_SHIELDED and tx.orchard_params is not None:
|
||||||
self.orchard = OrchardSigner(
|
self.orchard = OrchardSigner(
|
||||||
self.tx_info,
|
self.tx_info,
|
||||||
keychain.seed,
|
keychain.seed,
|
||||||
@ -63,10 +63,8 @@ class Zcash(Bitcoinlike):
|
|||||||
coin,
|
coin,
|
||||||
self.tx_req,
|
self.tx_req,
|
||||||
)
|
)
|
||||||
if self.orchard.inputs_count > 0 and tx.inputs_count > 0:
|
else:
|
||||||
raise DataError(
|
self.orchard = None
|
||||||
"Cannot spending transparent and Orchard inputs simultaneously is not supported."
|
|
||||||
)
|
|
||||||
|
|
||||||
def create_sig_hasher(self, tx: SignTx | PrevTx) -> ZcashHasher:
|
def create_sig_hasher(self, tx: SignTx | PrevTx) -> ZcashHasher:
|
||||||
return ZcashHasher(tx)
|
return ZcashHasher(tx)
|
||||||
@ -78,12 +76,12 @@ class Zcash(Bitcoinlike):
|
|||||||
|
|
||||||
async def step1_process_inputs(self):
|
async def step1_process_inputs(self):
|
||||||
await super().step1_process_inputs()
|
await super().step1_process_inputs()
|
||||||
if ZCASH_SHIELDED:
|
if ZCASH_SHIELDED and self.orchard is not None:
|
||||||
await self.orchard.process_inputs()
|
await self.orchard.process_inputs()
|
||||||
|
|
||||||
async def step2_approve_outputs(self):
|
async def step2_approve_outputs(self):
|
||||||
await super().step2_approve_outputs()
|
await super().step2_approve_outputs()
|
||||||
if ZCASH_SHIELDED:
|
if ZCASH_SHIELDED and self.orchard is not None:
|
||||||
await self.orchard.approve_outputs()
|
await self.orchard.approve_outputs()
|
||||||
|
|
||||||
async def step3_verify_inputs(self) -> None:
|
async def step3_verify_inputs(self) -> None:
|
||||||
@ -97,7 +95,7 @@ class Zcash(Bitcoinlike):
|
|||||||
self.taproot_only = False # turn off taproot behavior
|
self.taproot_only = False # turn off taproot behavior
|
||||||
|
|
||||||
async def step4_serialize_inputs(self):
|
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
|
# shield actions first to get a sighash
|
||||||
await self.orchard.compute_digest()
|
await self.orchard.compute_digest()
|
||||||
await super().step4_serialize_inputs()
|
await super().step4_serialize_inputs()
|
||||||
@ -111,14 +109,14 @@ class Zcash(Bitcoinlike):
|
|||||||
write_compact_size(self.serialized_tx, 0) # nOutputsSapling
|
write_compact_size(self.serialized_tx, 0) # nOutputsSapling
|
||||||
|
|
||||||
# nActionsOrchard
|
# nActionsOrchard
|
||||||
if ZCASH_SHIELDED:
|
if ZCASH_SHIELDED and self.orchard is not None::
|
||||||
write_compact_size(self.serialized_tx, self.orchard.actions_count)
|
write_compact_size(self.serialized_tx, self.orchard.actions_count)
|
||||||
else:
|
else:
|
||||||
write_compact_size(self.serialized_tx, 0)
|
write_compact_size(self.serialized_tx, 0)
|
||||||
|
|
||||||
async def step6_sign_segwit_inputs(self):
|
async def step6_sign_segwit_inputs(self):
|
||||||
# transparent inputs were signed in step 4
|
# transparent inputs were signed in step 4
|
||||||
if ZCASH_SHIELDED:
|
if ZCASH_SHIELDED and self.orchard is not None:
|
||||||
await self.orchard.sign_inputs()
|
await self.orchard.sign_inputs()
|
||||||
|
|
||||||
async def sign_nonsegwit_input(self, i_sign: int) -> None:
|
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:
|
def set_serialized_signature(self, i: int, signature: bytes) -> None:
|
||||||
super().set_serialized_signature(i, signature)
|
super().set_serialized_signature(i, signature)
|
||||||
assert self.tx_req.serialized is not None
|
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
|
self.tx_req.serialized.signature_type = ZcashSignatureType.TRANSPARENT
|
||||||
|
Loading…
Reference in New Issue
Block a user