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.
trezor-firmware/core/src/apps/zcash/unified_addresses.py

139 lines
4.3 KiB

"""
Implementation of encoding and decoding of Zcash
unified addresses according to the ZIP-316.
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
if TYPE_CHECKING:
from enum import IntEnum
else:
IntEnum = object
class Typecode(IntEnum):
P2PKH = 0x00
P2SH = 0x01
SAPLING = 0x02
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
def padding(hrp: str) -> bytes:
assert len(hrp) <= 16
return hrp.encode() + bytes(16 - len(hrp))
def encode(receivers: dict[Typecode, bytes], coin: CoinInfo) -> str:
# multiple transparent receivers forbidden
assert not (Typecode.P2PKH in receivers and Typecode.P2SH in receivers)
# at least one shielded address must be present
assert Typecode.SAPLING in receivers or Typecode.ORCHARD in receivers
length = 16 # 16 bytes for padding
for receiver_bytes in receivers.values():
length += 2 # typecode (1 byte) + length (1 byte)
length += len(receiver_bytes)
w = empty_bytearray(length)
# receivers in ascending order
receivers_list = list(receivers.items())
receivers_list.sort()
for (typecode, raw_bytes) in receivers_list:
length = receiver_length(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)
write_bytes_fixed(w, padding(hrp), 16)
f4jumble(memoryview(w))
converted = convertbits(w, 8, 5)
return bech32_encode(hrp, converted, Encoding.BECH32M)
def decode(addr_str: str, coin: CoinInfo) -> dict[int, bytes]:
try:
hrp, data, encoding = bech32_decode(addr_str, 1000)
except ValueError:
raise DataError("Bech32m decoding failed.")
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):
raise DataError("Unexpected address prefix.")
if encoding != Encoding.BECH32M:
raise DataError("Bech32m encoding required.")
decoded = bytearray(convertbits(data, 5, 8, False))
f4unjumble(memoryview(decoded))
# check trailing padding bytes
if decoded[-16:] != padding(hrp):
raise DataError("Invalid padding bytes")
r = BufferReader(decoded[:-16])
last_typecode = None
receivers: dict[int, bytes] = dict()
while r.remaining_count() > 0:
typecode = read_compact_size(r)
if typecode in receivers:
raise DataError("Duplicated typecode")
if typecode > 0x02000000:
raise DataError("Invalid typecode")
if last_typecode is not None and typecode < last_typecode:
raise DataError("Invalid receivers order")
last_typecode = typecode
length = read_compact_size(r)
# if the typecode of the receiver is known, then verify receiver length
expected_length = receiver_length(typecode)
if expected_length is not None and length != expected_length:
raise DataError("Unexpected receiver length")
if r.remaining_count() < length:
raise DataError("Invalid receiver length")
receivers[typecode] = r.read(length)
if Typecode.P2PKH in receivers and Typecode.P2SH in receivers:
raise DataError("Multiple transparent receivers")
if len(receivers) == 1:
the_receiver = list(receivers.keys())[0]
if the_receiver in (Typecode.P2PKH, Typecode.P2SH):
raise DataError("Only transparent receiver")
return receivers