mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-18 04:18:10 +00:00
chore(core): decrease zcash size by 300 bytes
This commit is contained in:
parent
164be3ac8f
commit
32125ef51f
@ -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:]
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user