mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-20 21:38:26 +00:00
feat!(core): support Zcash v5 transaction format
This implements: - ZIP-225: Version 5 Transaction Format - ZIP-244: Transaction Identifier Non-Malleability BREAKING CHANGE: Zcash prevouts are not verified (as in Taproot). Zcash replacement transactions are not supported.
This commit is contained in:
parent
670d11d627
commit
9985a72c6a
1
core/.changelog.d/2166.added
Normal file
1
core/.changelog.d/2166.added
Normal file
@ -0,0 +1 @@
|
|||||||
|
Support Zcash version 5 transaction format
|
@ -622,7 +622,7 @@ if FROZEN:
|
|||||||
exclude=[
|
exclude=[
|
||||||
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/decred.py',
|
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/decred.py',
|
||||||
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/bitcoinlike.py',
|
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/bitcoinlike.py',
|
||||||
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/zcash.py',
|
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/zcash_v4.py',
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -661,11 +661,13 @@ if FROZEN:
|
|||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/tezos/*.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/tezos/*.py'))
|
||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Tezos*.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Tezos*.py'))
|
||||||
|
|
||||||
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/*.py'))
|
||||||
|
|
||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/webauthn/*.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/webauthn/*.py'))
|
||||||
|
|
||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/decred.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/decred.py'))
|
||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/bitcoinlike.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/bitcoinlike.py'))
|
||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/zcash.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/zcash_v4.py'))
|
||||||
|
|
||||||
source_mpy = env.FrozenModule(source=SOURCE_PY, source_dir=SOURCE_PY_DIR, bitcoin_only=BITCOIN_ONLY)
|
source_mpy = env.FrozenModule(source=SOURCE_PY, source_dir=SOURCE_PY_DIR, bitcoin_only=BITCOIN_ONLY)
|
||||||
|
|
||||||
|
@ -586,7 +586,7 @@ if FROZEN:
|
|||||||
exclude=[
|
exclude=[
|
||||||
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/decred.py',
|
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/decred.py',
|
||||||
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/bitcoinlike.py',
|
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/bitcoinlike.py',
|
||||||
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/zcash.py',
|
SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/zcash_v4.py',
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -625,11 +625,13 @@ if FROZEN:
|
|||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/tezos/*.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/tezos/*.py'))
|
||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Tezos*.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Tezos*.py'))
|
||||||
|
|
||||||
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/zcash/*.py'))
|
||||||
|
|
||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/webauthn/*.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/webauthn/*.py'))
|
||||||
|
|
||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/decred.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/decred.py'))
|
||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/bitcoinlike.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/bitcoinlike.py'))
|
||||||
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/zcash.py'))
|
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/bitcoin/sign_tx/zcash_v4.py'))
|
||||||
|
|
||||||
source_mpy = env.FrozenModule(source=SOURCE_PY, source_dir=SOURCE_PY_DIR, bitcoin_only=BITCOIN_ONLY)
|
source_mpy = env.FrozenModule(source=SOURCE_PY, source_dir=SOURCE_PY_DIR, bitcoin_only=BITCOIN_ONLY)
|
||||||
|
|
||||||
|
@ -466,8 +466,8 @@ if not utils.BITCOIN_ONLY:
|
|||||||
import apps.binance.layout
|
import apps.binance.layout
|
||||||
apps.binance.sign_tx
|
apps.binance.sign_tx
|
||||||
import apps.binance.sign_tx
|
import apps.binance.sign_tx
|
||||||
apps.bitcoin.sign_tx.zcash
|
apps.bitcoin.sign_tx.zcash_v4
|
||||||
import apps.bitcoin.sign_tx.zcash
|
import apps.bitcoin.sign_tx.zcash_v4
|
||||||
apps.cardano
|
apps.cardano
|
||||||
import apps.cardano
|
import apps.cardano
|
||||||
apps.cardano.address
|
apps.cardano.address
|
||||||
@ -750,6 +750,12 @@ if not utils.BITCOIN_ONLY:
|
|||||||
import apps.webauthn.remove_resident_credential
|
import apps.webauthn.remove_resident_credential
|
||||||
apps.webauthn.resident_credentials
|
apps.webauthn.resident_credentials
|
||||||
import apps.webauthn.resident_credentials
|
import apps.webauthn.resident_credentials
|
||||||
|
apps.zcash
|
||||||
|
import apps.zcash
|
||||||
|
apps.zcash.hasher
|
||||||
|
import apps.zcash.hasher
|
||||||
|
apps.zcash.signer
|
||||||
|
import apps.zcash.signer
|
||||||
|
|
||||||
# generate full alphabet
|
# generate full alphabet
|
||||||
a
|
a
|
||||||
|
@ -9,7 +9,8 @@ from ..keychain import with_keychain
|
|||||||
from . import approvers, bitcoin, helpers, progress
|
from . import approvers, bitcoin, helpers, progress
|
||||||
|
|
||||||
if not utils.BITCOIN_ONLY:
|
if not utils.BITCOIN_ONLY:
|
||||||
from . import bitcoinlike, decred, zcash
|
from . import bitcoinlike, decred, zcash_v4
|
||||||
|
from apps.zcash.signer import Zcash
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
@ -70,7 +71,10 @@ async def sign_tx(
|
|||||||
if coin.decred:
|
if coin.decred:
|
||||||
signer_class = decred.Decred
|
signer_class = decred.Decred
|
||||||
elif coin.overwintered:
|
elif coin.overwintered:
|
||||||
signer_class = zcash.Zcashlike
|
if msg.version == 5:
|
||||||
|
signer_class = Zcash
|
||||||
|
else:
|
||||||
|
signer_class = zcash_v4.ZcashV4
|
||||||
else:
|
else:
|
||||||
signer_class = bitcoinlike.Bitcoinlike
|
signer_class = bitcoinlike.Bitcoinlike
|
||||||
|
|
||||||
|
@ -79,6 +79,13 @@ class DecredSigHasher:
|
|||||||
) -> bytes:
|
) -> bytes:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def hash_zip244(
|
||||||
|
self,
|
||||||
|
txi: TxInput | None,
|
||||||
|
script_pubkey: bytes | None,
|
||||||
|
) -> bytes:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class Decred(Bitcoin):
|
class Decred(Bitcoin):
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -39,6 +39,13 @@ if TYPE_CHECKING:
|
|||||||
) -> bytes:
|
) -> bytes:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
def hash_zip244(
|
||||||
|
self,
|
||||||
|
txi: TxInput | None,
|
||||||
|
script_pubkey: bytes | None,
|
||||||
|
) -> bytes:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
# BIP-0143 hash
|
# BIP-0143 hash
|
||||||
class BitcoinSigHasher:
|
class BitcoinSigHasher:
|
||||||
@ -166,3 +173,10 @@ class BitcoinSigHasher:
|
|||||||
writers.write_uint32(h_sigmsg, i)
|
writers.write_uint32(h_sigmsg, i)
|
||||||
|
|
||||||
return h_sigmsg.get_digest()
|
return h_sigmsg.get_digest()
|
||||||
|
|
||||||
|
def hash_zip244(
|
||||||
|
self,
|
||||||
|
txi: TxInput | None,
|
||||||
|
script_pubkey: bytes | None,
|
||||||
|
) -> bytes:
|
||||||
|
raise NotImplementedError
|
||||||
|
@ -35,7 +35,7 @@ if TYPE_CHECKING:
|
|||||||
OVERWINTERED = const(0x8000_0000)
|
OVERWINTERED = const(0x8000_0000)
|
||||||
|
|
||||||
|
|
||||||
class ZcashSigHasher:
|
class Zip243SigHasher:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.h_prevouts = HashWriter(blake2b(outlen=32, personal=b"ZcashPrevoutHash"))
|
self.h_prevouts = HashWriter(blake2b(outlen=32, personal=b"ZcashPrevoutHash"))
|
||||||
self.h_sequence = HashWriter(blake2b(outlen=32, personal=b"ZcashSequencHash"))
|
self.h_sequence = HashWriter(blake2b(outlen=32, personal=b"ZcashSequencHash"))
|
||||||
@ -113,8 +113,15 @@ class ZcashSigHasher:
|
|||||||
) -> bytes:
|
) -> bytes:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def hash_zip244(
|
||||||
|
self,
|
||||||
|
txi: TxInput | None,
|
||||||
|
script_pubkey: bytes | None,
|
||||||
|
) -> bytes:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
class Zcashlike(Bitcoinlike):
|
|
||||||
|
class ZcashV4(Bitcoinlike):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tx: SignTx,
|
tx: SignTx,
|
||||||
@ -129,7 +136,7 @@ class Zcashlike(Bitcoinlike):
|
|||||||
raise wire.DataError("Unsupported transaction version.")
|
raise wire.DataError("Unsupported transaction version.")
|
||||||
|
|
||||||
def create_sig_hasher(self, tx: SignTx | PrevTx) -> SigHasher:
|
def create_sig_hasher(self, tx: SignTx | PrevTx) -> SigHasher:
|
||||||
return ZcashSigHasher()
|
return Zip243SigHasher()
|
||||||
|
|
||||||
async def step7_finish(self) -> None:
|
async def step7_finish(self) -> None:
|
||||||
self.write_tx_footer(self.serialized_tx, self.tx_info.tx)
|
self.write_tx_footer(self.serialized_tx, self.tx_info.tx)
|
0
core/src/apps/zcash/__init__.py
Normal file
0
core/src/apps/zcash/__init__.py
Normal file
279
core/src/apps/zcash/hasher.py
Normal file
279
core/src/apps/zcash/hasher.py
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
"""
|
||||||
|
Implementation of Zcash txid and sighash algorithms
|
||||||
|
according to the ZIP-0244.
|
||||||
|
|
||||||
|
specification: https://zips.z.cash/zip-0244
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from trezor.crypto.hashlib import blake2b
|
||||||
|
from trezor.utils import HashWriter, empty_bytearray
|
||||||
|
|
||||||
|
from apps.bitcoin.common import SigHashType
|
||||||
|
from apps.bitcoin.writers import (
|
||||||
|
TX_HASH_SIZE,
|
||||||
|
write_bytes_fixed,
|
||||||
|
write_bytes_prefixed,
|
||||||
|
write_bytes_reversed,
|
||||||
|
write_tx_output,
|
||||||
|
write_uint8,
|
||||||
|
write_uint32,
|
||||||
|
write_uint64,
|
||||||
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from trezor.messages import TxInput, TxOutput, SignTx, PrevTx
|
||||||
|
from trezor.utils import Writer
|
||||||
|
from apps.common.coininfo import CoinInfo
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
||||||
|
def write_hash(w: Writer, hash: bytes) -> None:
|
||||||
|
write_bytes_fixed(w, hash, TX_HASH_SIZE)
|
||||||
|
|
||||||
|
|
||||||
|
def write_prevout(w: Writer, txi: TxInput) -> None:
|
||||||
|
write_bytes_reversed(w, txi.prev_hash, TX_HASH_SIZE)
|
||||||
|
write_uint32(w, txi.prev_index)
|
||||||
|
|
||||||
|
|
||||||
|
class ZcashHasher:
|
||||||
|
def __init__(self, tx: SignTx | PrevTx):
|
||||||
|
self.header = HeaderHasher(tx)
|
||||||
|
self.transparent = TransparentHasher()
|
||||||
|
self.sapling = SaplingHasher()
|
||||||
|
self.orchard = OrchardHasher()
|
||||||
|
|
||||||
|
assert tx.branch_id is not None # checked in sanitize_sign_tx
|
||||||
|
tx_hash_person = empty_bytearray(16)
|
||||||
|
write_bytes_fixed(tx_hash_person, b"ZcashTxHash_", 12)
|
||||||
|
write_uint32(tx_hash_person, tx.branch_id)
|
||||||
|
self.tx_hash_person = bytes(tx_hash_person)
|
||||||
|
|
||||||
|
# The `txid_digest` method is currently a dead code,
|
||||||
|
# but we keep it for future use cases.
|
||||||
|
def txid_digest(self) -> bytes:
|
||||||
|
"""
|
||||||
|
Returns the transaction identifier.
|
||||||
|
|
||||||
|
see: https://zips.z.cash/zip-0244#id4
|
||||||
|
"""
|
||||||
|
h = HashWriter(blake2b(outlen=32, personal=self.tx_hash_person))
|
||||||
|
|
||||||
|
write_hash(h, self.header.digest()) # T.1
|
||||||
|
write_hash(h, self.transparent.digest()) # T.2
|
||||||
|
write_hash(h, self.sapling.digest()) # T.3
|
||||||
|
write_hash(h, self.orchard.digest()) # T.4
|
||||||
|
|
||||||
|
return h.get_digest()
|
||||||
|
|
||||||
|
def signature_digest(
|
||||||
|
self, txi: TxInput | None, script_pubkey: bytes | None
|
||||||
|
) -> bytes:
|
||||||
|
"""
|
||||||
|
Returns the transaction signature digest.
|
||||||
|
|
||||||
|
see: https://zips.z.cash/zip-0244#id13
|
||||||
|
"""
|
||||||
|
h = HashWriter(blake2b(outlen=32, personal=self.tx_hash_person))
|
||||||
|
|
||||||
|
write_hash(h, self.header.digest()) # S.1
|
||||||
|
write_hash(h, self.transparent.sig_digest(txi, script_pubkey)) # S.2
|
||||||
|
write_hash(h, self.sapling.digest()) # S.3
|
||||||
|
write_hash(h, self.orchard.digest()) # S.4
|
||||||
|
|
||||||
|
return h.get_digest()
|
||||||
|
|
||||||
|
# implement `SigHasher` interface:
|
||||||
|
|
||||||
|
def add_input(self, txi: TxInput, script_pubkey: bytes) -> None:
|
||||||
|
self.transparent.add_input(txi, script_pubkey)
|
||||||
|
|
||||||
|
def add_output(self, txo: TxOutput, script_pubkey: bytes) -> None:
|
||||||
|
self.transparent.add_output(txo, script_pubkey)
|
||||||
|
|
||||||
|
def hash143(
|
||||||
|
self,
|
||||||
|
txi: TxInput,
|
||||||
|
public_keys: Sequence[bytes | memoryview],
|
||||||
|
threshold: int,
|
||||||
|
tx: SignTx | PrevTx,
|
||||||
|
coin: CoinInfo,
|
||||||
|
hash_type: int,
|
||||||
|
) -> bytes:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def hash341(
|
||||||
|
self,
|
||||||
|
i: int,
|
||||||
|
tx: SignTx | PrevTx,
|
||||||
|
sighash_type: SigHashType,
|
||||||
|
) -> bytes:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def hash_zip244(
|
||||||
|
self,
|
||||||
|
txi: TxInput | None,
|
||||||
|
script_pubkey: bytes | None,
|
||||||
|
) -> bytes:
|
||||||
|
return self.signature_digest(txi, script_pubkey)
|
||||||
|
|
||||||
|
|
||||||
|
class HeaderHasher:
|
||||||
|
def __init__(self, tx: SignTx | PrevTx):
|
||||||
|
h = HashWriter(blake2b(outlen=32, personal=b"ZTxIdHeadersHash"))
|
||||||
|
|
||||||
|
assert tx.version_group_id is not None
|
||||||
|
assert tx.branch_id is not None # checked in sanitize_*
|
||||||
|
assert tx.expiry is not None
|
||||||
|
|
||||||
|
write_uint32(h, tx.version | (1 << 31)) # T.1a
|
||||||
|
write_uint32(h, tx.version_group_id) # T.1b
|
||||||
|
write_uint32(h, tx.branch_id) # T.1c
|
||||||
|
write_uint32(h, tx.lock_time) # T.1d
|
||||||
|
write_uint32(h, tx.expiry) # T.1e
|
||||||
|
|
||||||
|
self._digest = h.get_digest()
|
||||||
|
|
||||||
|
def digest(self) -> bytes:
|
||||||
|
"""
|
||||||
|
Returns `T.1: header_digest` field.
|
||||||
|
|
||||||
|
see: https://zips.z.cash/zip-0244#t-1-header-digest
|
||||||
|
"""
|
||||||
|
return self._digest
|
||||||
|
|
||||||
|
|
||||||
|
class TransparentHasher:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.prevouts = HashWriter(
|
||||||
|
blake2b(outlen=32, personal=b"ZTxIdPrevoutHash")
|
||||||
|
) # a hasher for fields T.2a & S.2b
|
||||||
|
|
||||||
|
self.amounts = HashWriter(
|
||||||
|
blake2b(outlen=32, personal=b"ZTxTrAmountsHash")
|
||||||
|
) # a hasher for field S.2c
|
||||||
|
|
||||||
|
self.scriptpubkeys = HashWriter(
|
||||||
|
blake2b(outlen=32, personal=b"ZTxTrScriptsHash")
|
||||||
|
) # a hasher for field S.2d
|
||||||
|
|
||||||
|
self.sequence = HashWriter(
|
||||||
|
blake2b(outlen=32, personal=b"ZTxIdSequencHash")
|
||||||
|
) # a hasher for fields T.2b & S.2e
|
||||||
|
|
||||||
|
self.outputs = HashWriter(
|
||||||
|
blake2b(outlen=32, personal=b"ZTxIdOutputsHash")
|
||||||
|
) # a hasher for fields T.2c & S.2f
|
||||||
|
|
||||||
|
self.empty = True # inputs_amount + outputs_amount == 0
|
||||||
|
|
||||||
|
def add_input(self, txi: TxInput, script_pubkey: bytes) -> None:
|
||||||
|
self.empty = False
|
||||||
|
|
||||||
|
write_prevout(self.prevouts, txi)
|
||||||
|
write_uint64(self.amounts, txi.amount)
|
||||||
|
write_bytes_prefixed(self.scriptpubkeys, script_pubkey)
|
||||||
|
write_uint32(self.sequence, txi.sequence)
|
||||||
|
|
||||||
|
def add_output(self, txo: TxOutput, script_pubkey: bytes) -> None:
|
||||||
|
self.empty = False
|
||||||
|
|
||||||
|
write_tx_output(self.outputs, txo, script_pubkey)
|
||||||
|
|
||||||
|
def digest(self) -> bytes:
|
||||||
|
"""
|
||||||
|
Returns `T.2: transparent_digest` field for txid computation.
|
||||||
|
|
||||||
|
see: https://zips.z.cash/zip-0244#t-2-transparent-digest
|
||||||
|
"""
|
||||||
|
h = HashWriter(blake2b(outlen=32, personal=b"ZTxIdTranspaHash"))
|
||||||
|
|
||||||
|
if not self.empty:
|
||||||
|
write_hash(h, self.prevouts.get_digest()) # T.2a
|
||||||
|
write_hash(h, self.sequence.get_digest()) # T.2b
|
||||||
|
write_hash(h, self.outputs.get_digest()) # T.2c
|
||||||
|
|
||||||
|
return h.get_digest()
|
||||||
|
|
||||||
|
def sig_digest(
|
||||||
|
self,
|
||||||
|
txi: TxInput | None,
|
||||||
|
script_pubkey: bytes | None,
|
||||||
|
) -> bytes:
|
||||||
|
"""
|
||||||
|
Returns `S.2: transparent_sig_digest` field for signature
|
||||||
|
digest computation.
|
||||||
|
|
||||||
|
see: https://zips.z.cash/zip-0244#s-2-transparent-sig-digest
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.empty:
|
||||||
|
assert txi is None
|
||||||
|
assert script_pubkey is None
|
||||||
|
return self.digest()
|
||||||
|
|
||||||
|
h = HashWriter(blake2b(outlen=32, personal=b"ZTxIdTranspaHash"))
|
||||||
|
|
||||||
|
# only SIGHASH_ALL is supported in Trezor
|
||||||
|
write_uint8(h, SigHashType.SIGHASH_ALL) # S.2a
|
||||||
|
write_hash(h, self.prevouts.get_digest()) # S.2b
|
||||||
|
write_hash(h, self.amounts.get_digest()) # S.2c
|
||||||
|
write_hash(h, self.scriptpubkeys.get_digest()) # S.2d
|
||||||
|
write_hash(h, self.sequence.get_digest()) # S.2e
|
||||||
|
write_hash(h, self.outputs.get_digest()) # S.2f
|
||||||
|
write_hash(h, _txin_sig_digest(txi, script_pubkey)) # S.2g
|
||||||
|
|
||||||
|
return h.get_digest()
|
||||||
|
|
||||||
|
|
||||||
|
def _txin_sig_digest(
|
||||||
|
txi: TxInput | None,
|
||||||
|
script_pubkey: bytes | None,
|
||||||
|
) -> bytes:
|
||||||
|
"""
|
||||||
|
Returns `S.2g: txin_sig_digest` field for signature digest computation.
|
||||||
|
|
||||||
|
see: https://zips.z.cash/zip-0244#s-2g-txin-sig-digest
|
||||||
|
"""
|
||||||
|
|
||||||
|
h = HashWriter(blake2b(outlen=32, personal=b"Zcash___TxInHash"))
|
||||||
|
|
||||||
|
if txi is not None:
|
||||||
|
assert script_pubkey is not None
|
||||||
|
|
||||||
|
write_prevout(h, txi) # 2.Sg.i
|
||||||
|
write_uint64(h, txi.amount) # 2.Sg.ii
|
||||||
|
write_bytes_prefixed(h, script_pubkey) # 2.Sg.iii
|
||||||
|
write_uint32(h, txi.sequence) # 2.Sg.iv
|
||||||
|
|
||||||
|
return h.get_digest()
|
||||||
|
|
||||||
|
|
||||||
|
class SaplingHasher:
|
||||||
|
"""
|
||||||
|
Empty Sapling bundle hasher.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def digest(self) -> bytes:
|
||||||
|
"""
|
||||||
|
Returns `T.3: sapling_digest` field.
|
||||||
|
|
||||||
|
see: https://zips.z.cash/zip-0244#t-3-sapling-digest
|
||||||
|
"""
|
||||||
|
return blake2b(outlen=32, personal=b"ZTxIdSaplingHash").digest()
|
||||||
|
|
||||||
|
|
||||||
|
class OrchardHasher:
|
||||||
|
"""
|
||||||
|
Empty Orchard bundle hasher.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def digest(self) -> bytes:
|
||||||
|
"""
|
||||||
|
Returns `T.4: orchard_digest` field.
|
||||||
|
|
||||||
|
see: https://zips.z.cash/zip-0244#t-4-orchard-digest
|
||||||
|
"""
|
||||||
|
return blake2b(outlen=32, personal=b"ZTxIdOrchardHash").digest()
|
112
core/src/apps/zcash/signer.py
Normal file
112
core/src/apps/zcash/signer.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
from micropython import const
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from trezor.messages import SignTx
|
||||||
|
from trezor.utils import ensure
|
||||||
|
from trezor.wire import DataError, ProcessError
|
||||||
|
|
||||||
|
from apps.bitcoin.common import ecdsa_sign
|
||||||
|
from apps.bitcoin.sign_tx.bitcoinlike import Bitcoinlike
|
||||||
|
from apps.common.writers import write_compact_size, write_uint32_le
|
||||||
|
|
||||||
|
from .hasher import ZcashHasher
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
from apps.bitcoin.keychain import Keychain
|
||||||
|
|
||||||
|
OVERWINTERED = const(0x8000_0000)
|
||||||
|
|
||||||
|
|
||||||
|
class Zcash(Bitcoinlike):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
tx: SignTx,
|
||||||
|
keychain: Keychain,
|
||||||
|
coin: CoinInfo,
|
||||||
|
approver: Approver | None,
|
||||||
|
) -> None:
|
||||||
|
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:
|
||||||
|
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]:
|
||||||
|
signature_digest = self.tx_info.sig_hasher.hash_zip244(
|
||||||
|
txi, self.input_derive_script(txi)
|
||||||
|
)
|
||||||
|
node = self.keychain.derive(txi.address_n)
|
||||||
|
signature = ecdsa_sign(node, signature_digest)
|
||||||
|
return node.public_key(), signature
|
||||||
|
|
||||||
|
async def process_original_input(self, txi: TxInput, script_pubkey: bytes) -> None:
|
||||||
|
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:
|
||||||
|
# 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
|
||||||
|
|
||||||
|
write_uint32_le(w, tx.version | OVERWINTERED) # nVersion | fOverwintered
|
||||||
|
write_uint32_le(w, tx.version_group_id) # nVersionGroupId
|
||||||
|
write_uint32_le(w, tx.branch_id) # nConsensusBranchId
|
||||||
|
write_uint32_le(w, tx.lock_time) # lock_time
|
||||||
|
write_uint32_le(w, tx.expiry) # expiryHeight
|
||||||
|
|
||||||
|
def write_tx_footer(self, w: Writer, tx: SignTx | PrevTx) -> None:
|
||||||
|
# serialize Sapling bundle
|
||||||
|
write_compact_size(w, 0) # nSpendsSapling
|
||||||
|
write_compact_size(w, 0) # nOutputsSapling
|
||||||
|
# serialize Orchard bundle
|
||||||
|
write_compact_size(w, 0) # nActionsOrchard
|
@ -9,7 +9,7 @@ from apps.bitcoin.common import SigHashType
|
|||||||
from apps.bitcoin.writers import get_tx_hash
|
from apps.bitcoin.writers import get_tx_hash
|
||||||
|
|
||||||
if not utils.BITCOIN_ONLY:
|
if not utils.BITCOIN_ONLY:
|
||||||
from apps.bitcoin.sign_tx.zcash import ZcashSigHasher
|
from apps.bitcoin.sign_tx.zcash_v4 import Zip243SigHasher
|
||||||
|
|
||||||
|
|
||||||
# test vectors inspired from https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zip_0243.py
|
# test vectors inspired from https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zip_0243.py
|
||||||
@ -191,7 +191,7 @@ class TestZcashZip243(unittest.TestCase):
|
|||||||
branch_id=v["branch_id"],
|
branch_id=v["branch_id"],
|
||||||
)
|
)
|
||||||
|
|
||||||
zip243 = ZcashSigHasher()
|
zip243 = Zip243SigHasher()
|
||||||
|
|
||||||
for i in v["inputs"]:
|
for i in v["inputs"]:
|
||||||
txi = TxInput(
|
txi = TxInput(
|
||||||
|
Loading…
Reference in New Issue
Block a user