1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-17 21:22:10 +00:00

chore(core): decrease zcash size by 300 bytes

This commit is contained in:
grdddj 2022-09-17 17:04:49 +02:00 committed by matejcik
parent 164be3ac8f
commit 32125ef51f
5 changed files with 113 additions and 82 deletions

View File

@ -8,7 +8,7 @@ from micropython import const
from trezor.crypto.hashlib import blake2b
HASH_LENGTH = const(64)
_HASH_LENGTH = const(64)
def xor(target: memoryview, mask: bytes) -> None:
@ -17,12 +17,12 @@ def xor(target: memoryview, mask: bytes) -> None:
def G_round(i: int, left: memoryview, right: memoryview) -> None:
for j in range((len(right) + HASH_LENGTH - 1) // HASH_LENGTH):
for j in range((len(right) + _HASH_LENGTH - 1) // _HASH_LENGTH):
mask = blake2b(
personal=b"UA_F4Jumble_G" + bytes([i]) + j.to_bytes(2, "little"),
data=bytes(left),
).digest()
xor(right[j * HASH_LENGTH : (j + 1) * HASH_LENGTH], mask)
xor(right[j * _HASH_LENGTH : (j + 1) * _HASH_LENGTH], mask)
def H_round(i: int, left: memoryview, right: memoryview) -> None:
@ -36,7 +36,7 @@ def H_round(i: int, left: memoryview, right: memoryview) -> None:
def f4jumble(message: memoryview) -> None:
assert 48 <= len(message) <= 4194368
left_length = min(HASH_LENGTH, len(message) // 2)
left_length = min(_HASH_LENGTH, len(message) // 2)
left = message[:left_length]
right = message[left_length:]
@ -48,7 +48,7 @@ def f4jumble(message: memoryview) -> None:
def f4unjumble(message: memoryview) -> None:
assert 48 <= len(message) <= 4194368
left_length = min(HASH_LENGTH, len(message) // 2)
left_length = min(_HASH_LENGTH, len(message) // 2)
left = message[:left_length]
right = message[left_length:]

View File

@ -8,9 +8,7 @@ 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,
@ -23,10 +21,11 @@ from apps.bitcoin.writers import (
)
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
from trezor.messages import TxInput, TxOutput, SignTx, PrevTx
from trezor.utils import Writer, HashWriter
from apps.common.coininfo import CoinInfo
from apps.bitcoin.common import SigHashType
def write_hash(w: Writer, hash: bytes) -> None:
@ -38,8 +37,16 @@ def write_prevout(w: Writer, txi: TxInput) -> None:
write_uint32(w, txi.prev_index)
def blake_hash_writer_32(personal: bytes) -> HashWriter:
from trezor.utils import HashWriter
return HashWriter(blake2b(outlen=32, personal=personal))
class ZcashHasher:
def __init__(self, tx: SignTx | PrevTx):
from trezor.utils import empty_bytearray
self.header = HeaderHasher(tx)
self.transparent = TransparentHasher()
self.sapling = SaplingHasher()
@ -51,6 +58,23 @@ class ZcashHasher:
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.
if False:
def txid_digest(self) -> bytes:
"""
Returns the transaction identifier.
see: https://zips.z.cash/zip-0244#id4
"""
h = blake_hash_writer_32(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
def signature_digest(
self, txi: TxInput | None, script_pubkey: bytes | None
) -> bytes:
@ -59,7 +83,7 @@ class ZcashHasher:
see: https://zips.z.cash/zip-0244#id13
"""
h = HashWriter(blake2b(outlen=32, personal=self.tx_hash_person))
h = blake_hash_writer_32(self.tx_hash_person)
write_hash(h, self.header.digest()) # S.1
write_hash(h, self.transparent.sig_digest(txi, script_pubkey)) # S.2
@ -105,7 +129,7 @@ class ZcashHasher:
class HeaderHasher:
def __init__(self, tx: SignTx | PrevTx):
h = HashWriter(blake2b(outlen=32, personal=b"ZTxIdHeadersHash"))
h = blake_hash_writer_32(b"ZTxIdHeadersHash")
assert tx.version_group_id is not None
assert tx.branch_id is not None # checked in sanitize_*
@ -130,25 +154,20 @@ class HeaderHasher:
class TransparentHasher:
def __init__(self) -> None:
self.prevouts = HashWriter(
blake2b(outlen=32, personal=b"ZTxIdPrevoutHash")
) # a hasher for fields T.2a & S.2b
# a hasher for fields T.2a & S.2b
self.prevouts = blake_hash_writer_32(b"ZTxIdPrevoutHash")
self.amounts = HashWriter(
blake2b(outlen=32, personal=b"ZTxTrAmountsHash")
) # a hasher for field S.2c
# a hasher for field S.2c
self.amounts = blake_hash_writer_32(b"ZTxTrAmountsHash")
self.scriptpubkeys = HashWriter(
blake2b(outlen=32, personal=b"ZTxTrScriptsHash")
) # a hasher for field S.2d
# a hasher for field S.2d
self.scriptpubkeys = blake_hash_writer_32(b"ZTxTrScriptsHash")
self.sequence = HashWriter(
blake2b(outlen=32, personal=b"ZTxIdSequencHash")
) # a hasher for fields T.2b & S.2e
# a hasher for fields T.2b & S.2e
self.sequence = blake_hash_writer_32(b"ZTxIdSequencHash")
self.outputs = HashWriter(
blake2b(outlen=32, personal=b"ZTxIdOutputsHash")
) # a hasher for fields T.2c & S.2f
# a hasher for fields T.2c & S.2f
self.outputs = blake_hash_writer_32(b"ZTxIdOutputsHash")
self.empty = True # inputs_amount + outputs_amount == 0
@ -171,7 +190,7 @@ class TransparentHasher:
see: https://zips.z.cash/zip-0244#t-2-transparent-digest
"""
h = HashWriter(blake2b(outlen=32, personal=b"ZTxIdTranspaHash"))
h = blake_hash_writer_32(b"ZTxIdTranspaHash")
if not self.empty:
write_hash(h, self.prevouts.get_digest()) # T.2a
@ -191,13 +210,14 @@ class TransparentHasher:
see: https://zips.z.cash/zip-0244#s-2-transparent-sig-digest
"""
from apps.bitcoin.common import SigHashType
if self.empty:
assert txi is None
assert script_pubkey is None
return self.digest()
h = HashWriter(blake2b(outlen=32, personal=b"ZTxIdTranspaHash"))
h = blake_hash_writer_32(b"ZTxIdTranspaHash")
# only SIGHASH_ALL is supported in Trezor
write_uint8(h, SigHashType.SIGHASH_ALL) # S.2a
@ -221,7 +241,7 @@ def _txin_sig_digest(
see: https://zips.z.cash/zip-0244#s-2g-txin-sig-digest
"""
h = HashWriter(blake2b(outlen=32, personal=b"Zcash___TxInHash"))
h = blake_hash_writer_32(b"Zcash___TxInHash")
if txi is not None:
assert script_pubkey is not None

View File

@ -1,19 +1,9 @@
from micropython import const
from typing import TYPE_CHECKING
from trezor.enums import OutputScriptType
from trezor.messages import SignTx
from trezor.utils import ensure
from trezor.wire import DataError, ProcessError
from trezor.wire import DataError
from apps.bitcoin import scripts
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 . import unified_addresses
from .hasher import ZcashHasher
from .unified_addresses import Typecode
if TYPE_CHECKING:
from typing import Sequence
@ -22,12 +12,9 @@ if TYPE_CHECKING:
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,
)
from trezor.messages import PrevTx, TxInput, TxOutput, SignTx
from apps.bitcoin.keychain import Keychain
from .hasher import ZcashHasher
_OVERWINTERED = const(0x8000_0000)
@ -40,6 +27,8 @@ class Zcash(Bitcoinlike):
coin: CoinInfo,
approver: Approver | None,
) -> None:
from trezor.utils import ensure
ensure(coin.overwintered)
if tx.version != 5:
raise DataError("Expected transaction version 5.")
@ -47,6 +36,8 @@ class Zcash(Bitcoinlike):
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:
@ -71,6 +62,8 @@ class Zcash(Bitcoinlike):
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)
@ -79,6 +72,8 @@ class Zcash(Bitcoinlike):
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.
@ -98,18 +93,25 @@ class Zcash(Bitcoinlike):
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
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
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
@ -117,11 +119,15 @@ class Zcash(Bitcoinlike):
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 = unified_addresses.decode(txo.address, self.coin)
receivers = decode(txo.address, self.coin)
if Typecode.P2PKH in receivers:
pubkeyhash = receivers[Typecode.P2PKH]
return scripts.output_script_p2pkh(pubkeyhash)

View File

@ -6,22 +6,23 @@ see: https://zips.z.cash/zip-0316
from typing import TYPE_CHECKING
from trezor.crypto.bech32 import Encoding, bech32_decode, bech32_encode, convertbits
from trezor.utils import BufferReader, empty_bytearray
from trezor.wire import DataError
from apps.common.coininfo import CoinInfo
from apps.common.readers import read_compact_size
from apps.common.writers import write_bytes_fixed, write_compact_size
from .f4jumble import f4jumble, f4unjumble
from trezor.crypto.bech32 import Encoding, convertbits
if TYPE_CHECKING:
from enum import IntEnum
from apps.common.coininfo import CoinInfo
else:
IntEnum = object
# Saves 50 bytes over `def prefix(coin: CoinInfo) -> str`
# (throws KeyError instead of ValueError but it should not matter)
PREFIXES = {
"Zcash": "u",
"Zcash Testnet": "utest",
}
class Typecode(IntEnum):
P2PKH = 0x00
P2SH = 0x01
@ -29,22 +30,14 @@ class Typecode(IntEnum):
ORCHARD = 0x03
def receiver_length(typecode: int) -> int | None:
"""Byte length of a receiver."""
if typecode in (Typecode.P2PKH, Typecode.P2SH):
return 20
if typecode in (Typecode.SAPLING, Typecode.ORCHARD):
return 43
return None
def prefix(coin: CoinInfo) -> str:
"""Prefix for a unified address."""
if coin.coin_name == "Zcash":
return "u"
if coin.coin_name == "Zcash Testnet":
return "utest"
raise ValueError
# Byte length of a receiver.
# Saves 30 bytes over `def receiver_length(typecode: Typecode) -> int`
RECEIVER_LENGTHS = {
Typecode.P2PKH: 20,
Typecode.P2SH: 20,
Typecode.SAPLING: 43,
Typecode.ORCHARD: 43,
}
def padding(hrp: str) -> bytes:
@ -53,6 +46,11 @@ def padding(hrp: str) -> bytes:
def encode(receivers: dict[Typecode, bytes], coin: CoinInfo) -> str:
from trezor.crypto.bech32 import bech32_encode
from trezor.utils import empty_bytearray
from .f4jumble import f4jumble
from apps.common.writers import write_bytes_fixed, write_compact_size
# multiple transparent receivers forbidden
assert not (Typecode.P2PKH in receivers and Typecode.P2SH in receivers)
# at least one shielded address must be present
@ -70,12 +68,12 @@ def encode(receivers: dict[Typecode, bytes], coin: CoinInfo) -> str:
receivers_list.sort()
for (typecode, raw_bytes) in receivers_list:
length = receiver_length(typecode) or len(raw_bytes)
length = RECEIVER_LENGTHS.get(typecode) or len(raw_bytes)
write_compact_size(w, typecode)
write_compact_size(w, length)
write_bytes_fixed(w, raw_bytes, length)
hrp = prefix(coin)
hrp = PREFIXES[coin.coin_name]
write_bytes_fixed(w, padding(hrp), 16)
f4jumble(memoryview(w))
converted = convertbits(w, 8, 5)
@ -83,6 +81,12 @@ def encode(receivers: dict[Typecode, bytes], coin: CoinInfo) -> str:
def decode(addr_str: str, coin: CoinInfo) -> dict[int, bytes]:
from trezor.crypto.bech32 import bech32_decode
from trezor.utils import BufferReader
from trezor.wire import DataError
from apps.common.readers import read_compact_size
from .f4jumble import f4unjumble
try:
hrp, data, encoding = bech32_decode(addr_str, 1000)
except ValueError:
@ -90,7 +94,7 @@ def decode(addr_str: str, coin: CoinInfo) -> dict[int, bytes]:
assert hrp is not None # to satisfy typecheckers
assert data is not None # to satisfy typecheckers
assert encoding is not None # to satisfy typecheckers
if hrp != prefix(coin):
if hrp != PREFIXES[coin.coin_name]:
raise DataError("Unexpected address prefix.")
if encoding != Encoding.BECH32M:
raise DataError("Bech32m encoding required.")
@ -118,7 +122,7 @@ def decode(addr_str: str, coin: CoinInfo) -> dict[int, bytes]:
length = read_compact_size(r)
# if the typecode of the receiver is known, then verify receiver length
expected_length = receiver_length(typecode)
expected_length = RECEIVER_LENGTHS.get(Typecode(typecode))
if expected_length is not None and length != expected_length:
raise DataError("Unexpected receiver length")

View File

@ -1,7 +1,8 @@
from common import *
from trezor.messages import TxInput, SignTx, PrevOutput
from trezor.enums import InputScriptType
from apps.zcash.hasher import ZcashHasher, HashWriter, blake2b, write_hash
from trezor.utils import HashWriter
from apps.zcash.hasher import ZcashHasher, blake2b, write_hash
from apps.common.coininfo import by_name