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.
139 lines
4.3 KiB
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
|