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.
283 lines
8.1 KiB
283 lines
8.1 KiB
"""
|
|
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 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 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:
|
|
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)
|
|
|
|
|
|
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()
|
|
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.
|
|
if False: # noqa
|
|
|
|
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:
|
|
"""
|
|
Returns the transaction signature digest.
|
|
|
|
see: https://zips.z.cash/zip-0244#id13
|
|
"""
|
|
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
|
|
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 = blake_hash_writer_32(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:
|
|
# a hasher for fields T.2a & S.2b
|
|
self.prevouts = blake_hash_writer_32(b"ZTxIdPrevoutHash")
|
|
|
|
# a hasher for field S.2c
|
|
self.amounts = blake_hash_writer_32(b"ZTxTrAmountsHash")
|
|
|
|
# a hasher for field S.2d
|
|
self.scriptpubkeys = blake_hash_writer_32(b"ZTxTrScriptsHash")
|
|
|
|
# a hasher for fields T.2b & S.2e
|
|
self.sequence = blake_hash_writer_32(b"ZTxIdSequencHash")
|
|
|
|
# a hasher for fields T.2c & S.2f
|
|
self.outputs = blake_hash_writer_32(b"ZTxIdOutputsHash")
|
|
|
|
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 = blake_hash_writer_32(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
|
|
"""
|
|
from apps.bitcoin.common import SigHashType
|
|
|
|
if self.empty:
|
|
assert txi is None
|
|
assert script_pubkey is None
|
|
return self.digest()
|
|
|
|
h = blake_hash_writer_32(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 = blake_hash_writer_32(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()
|