You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
4.9 KiB
141 lines
4.9 KiB
from micropython import const
|
|
from typing import TYPE_CHECKING
|
|
|
|
from trezor.wire import DataError
|
|
|
|
from apps.bitcoin.sign_tx.bitcoinlike import Bitcoinlike
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Sequence
|
|
from apps.common.coininfo import CoinInfo
|
|
from apps.bitcoin.sign_tx.tx_info import OriginalTxInfo, TxInfo
|
|
from apps.bitcoin.writers import Writer
|
|
from apps.bitcoin.sign_tx.approvers import Approver
|
|
from trezor.utils import HashWriter
|
|
from trezor.messages import PrevTx, TxInput, TxOutput, SignTx
|
|
from apps.bitcoin.keychain import Keychain
|
|
from .hasher import ZcashHasher
|
|
|
|
_OVERWINTERED = const(0x8000_0000)
|
|
|
|
|
|
class Zcash(Bitcoinlike):
|
|
def __init__(
|
|
self,
|
|
tx: SignTx,
|
|
keychain: Keychain,
|
|
coin: CoinInfo,
|
|
approver: Approver | None,
|
|
) -> None:
|
|
from trezor.utils import ensure
|
|
|
|
ensure(coin.overwintered)
|
|
if tx.version != 5:
|
|
raise DataError("Expected transaction version 5.")
|
|
|
|
super().__init__(tx, keychain, coin, approver)
|
|
|
|
def create_sig_hasher(self, tx: SignTx | PrevTx) -> ZcashHasher:
|
|
from .hasher import ZcashHasher
|
|
|
|
return ZcashHasher(tx)
|
|
|
|
def create_hash_writer(self) -> HashWriter:
|
|
# Replacement transactions are not supported
|
|
# so this should never be called.
|
|
raise NotImplementedError
|
|
|
|
async def step3_verify_inputs(self) -> None:
|
|
# Replacement transactions are not supported.
|
|
|
|
# We don't check prevouts, because BIP-341 techniques
|
|
# were adapted in ZIP-244 sighash algorithm.
|
|
# see: https://github.com/zcash/zips/issues/574
|
|
self.taproot_only = True # turn on taproot behavior
|
|
await super().step3_verify_inputs()
|
|
self.taproot_only = False # turn off taproot behavior
|
|
|
|
async def step5_serialize_outputs(self) -> None:
|
|
await super().step5_serialize_outputs()
|
|
|
|
async def sign_nonsegwit_input(self, i_sign: int) -> None:
|
|
await self.sign_nonsegwit_bip143_input(i_sign)
|
|
|
|
def sign_bip143_input(self, i: int, txi: TxInput) -> tuple[bytes, bytes]:
|
|
from apps.bitcoin.common import ecdsa_sign
|
|
|
|
node = self.keychain.derive(txi.address_n)
|
|
signature_digest = self.tx_info.sig_hasher.hash_zip244(
|
|
txi, self.input_derive_script(txi, node)
|
|
)
|
|
signature = ecdsa_sign(node, signature_digest)
|
|
return node.public_key(), signature
|
|
|
|
async def process_original_input(self, txi: TxInput, script_pubkey: bytes) -> None:
|
|
from trezor.wire import ProcessError
|
|
|
|
raise ProcessError("Replacement transactions are not supported.")
|
|
# Zcash transaction fees are very low
|
|
# so there is no need to bump the fee.
|
|
|
|
async def get_tx_digest(
|
|
self,
|
|
i: int,
|
|
txi: TxInput,
|
|
tx_info: TxInfo | OriginalTxInfo,
|
|
public_keys: Sequence[bytes | memoryview],
|
|
threshold: int,
|
|
script_pubkey: bytes,
|
|
tx_hash: bytes | None = None,
|
|
) -> bytes:
|
|
return self.tx_info.sig_hasher.hash_zip244(txi, script_pubkey)
|
|
|
|
def write_tx_header(
|
|
self, w: Writer, tx: SignTx | PrevTx, witness_marker: bool
|
|
) -> None:
|
|
from apps.common.writers import write_uint32_le
|
|
|
|
# defined in ZIP-225 (see https://zips.z.cash/zip-0225)
|
|
assert tx.version_group_id is not None
|
|
assert tx.branch_id is not None # checked in sanitize_*
|
|
assert tx.expiry is not None
|
|
|
|
for num in (
|
|
tx.version | _OVERWINTERED, # nVersion | fOverwintered
|
|
tx.version_group_id, # nVersionGroupId
|
|
tx.branch_id, # nConsensusBranchId
|
|
tx.lock_time, # lock_time
|
|
tx.expiry, # expiryHeight
|
|
):
|
|
write_uint32_le(w, num)
|
|
|
|
def write_tx_footer(self, w: Writer, tx: SignTx | PrevTx) -> None:
|
|
from apps.common.writers import write_compact_size
|
|
|
|
# serialize Sapling bundle
|
|
write_compact_size(w, 0) # nSpendsSapling
|
|
write_compact_size(w, 0) # nOutputsSapling
|
|
# serialize Orchard bundle
|
|
write_compact_size(w, 0) # nActionsOrchard
|
|
|
|
def output_derive_script(self, txo: TxOutput) -> bytes:
|
|
from apps.bitcoin import scripts
|
|
from trezor.enums import OutputScriptType
|
|
from .unified_addresses import Typecode, decode
|
|
|
|
# unified addresses
|
|
if txo.address is not None and txo.address[0] == "u":
|
|
assert txo.script_type is OutputScriptType.PAYTOADDRESS
|
|
|
|
receivers = decode(txo.address, self.coin)
|
|
if Typecode.P2PKH in receivers:
|
|
pubkeyhash = receivers[Typecode.P2PKH]
|
|
return scripts.output_script_p2pkh(pubkeyhash)
|
|
if Typecode.P2SH in receivers:
|
|
scripthash = receivers[Typecode.P2SH]
|
|
return scripts.output_script_p2sh(scripthash)
|
|
raise DataError("Unified address does not include a transparent receiver.")
|
|
|
|
# transparent addresses
|
|
return super().output_derive_script(txo)
|