mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-08 22:40:59 +00:00
core/sign_tx: Move non-Bitcoin functionality to Bitcoinlike class.
This commit is contained in:
parent
613c81ea66
commit
c190eed7fc
@ -16,16 +16,20 @@ from apps.wallet.sign_tx import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not utils.BITCOIN_ONLY:
|
if not utils.BITCOIN_ONLY:
|
||||||
from apps.wallet.sign_tx import decred
|
from apps.wallet.sign_tx import decred, bitcoinlike
|
||||||
|
|
||||||
|
|
||||||
async def sign_tx(ctx, msg, keychain):
|
async def sign_tx(ctx, msg, keychain):
|
||||||
coin_name = msg.coin_name if msg.coin_name is not None else "Bitcoin"
|
coin_name = msg.coin_name if msg.coin_name is not None else "Bitcoin"
|
||||||
coin = coins.by_name(coin_name)
|
coin = coins.by_name(coin_name)
|
||||||
if not utils.BITCOIN_ONLY and coin.decred:
|
if not utils.BITCOIN_ONLY and coin.decred:
|
||||||
signer = decred.Decred().signer(msg, keychain, coin)
|
coinsig = decred.Decred()
|
||||||
|
elif not utils.BITCOIN_ONLY and coin_name not in ("Bitcoin", "Regtest", "Testnet"):
|
||||||
|
coinsig = bitcoinlike.Bitcoinlike()
|
||||||
else:
|
else:
|
||||||
signer = signing.Bitcoin().signer(msg, keychain, coin)
|
coinsig = signing.Bitcoin()
|
||||||
|
|
||||||
|
signer = coinsig.signer(msg, keychain, coin)
|
||||||
|
|
||||||
res = None
|
res = None
|
||||||
while True:
|
while True:
|
||||||
|
183
core/src/apps/wallet/sign_tx/bitcoinlike.py
Normal file
183
core/src/apps/wallet/sign_tx/bitcoinlike.py
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import gc
|
||||||
|
from micropython import const
|
||||||
|
|
||||||
|
from trezor.crypto import cashaddr
|
||||||
|
from trezor.messages import FailureType, InputScriptType
|
||||||
|
from trezor.messages.SignTx import SignTx
|
||||||
|
from trezor.messages.TransactionType import TransactionType
|
||||||
|
from trezor.messages.TxInputType import TxInputType
|
||||||
|
from trezor.messages.TxOutputType import TxOutputType
|
||||||
|
from trezor.messages.TxRequestSerializedType import TxRequestSerializedType
|
||||||
|
|
||||||
|
from apps.wallet.sign_tx import addresses, helpers, multisig, signing, writers, zcash
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
|
class Bitcoinlike(signing.Bitcoin):
|
||||||
|
def init_hash143(self) -> None:
|
||||||
|
if self.coin.overwintered:
|
||||||
|
if self.tx.version == 3:
|
||||||
|
branch_id = self.tx.branch_id or 0x5BA81B19 # Overwinter
|
||||||
|
self.hash143 = zcash.Zip143(branch_id) # ZIP-0143 transaction hashing
|
||||||
|
elif self.tx.version == 4:
|
||||||
|
branch_id = self.tx.branch_id or 0x76B809BB # Sapling
|
||||||
|
self.hash143 = zcash.Zip243(branch_id) # ZIP-0243 transaction hashing
|
||||||
|
else:
|
||||||
|
raise signing.SigningError(
|
||||||
|
FailureType.DataError,
|
||||||
|
"Unsupported version for overwintered transaction",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
super().init_hash143()
|
||||||
|
|
||||||
|
async def phase1_process_segwit_input(self, i: int, txi: TxInputType) -> None:
|
||||||
|
if not self.coin.segwit:
|
||||||
|
raise signing.SigningError(
|
||||||
|
FailureType.DataError, "Segwit not enabled on this coin"
|
||||||
|
)
|
||||||
|
await super().phase1_process_segwit_input(i, txi)
|
||||||
|
|
||||||
|
async def phase1_process_nonsegwit_input(self, i: int, txi: TxInputType) -> None:
|
||||||
|
if self.coin.force_bip143 or self.coin.overwintered:
|
||||||
|
await self.phase1_process_bip143_input(i, txi)
|
||||||
|
else:
|
||||||
|
await super().phase1_process_nonsegwit_input(i, txi)
|
||||||
|
|
||||||
|
async def phase1_process_bip143_input(self, i: int, txi: TxInputType) -> None:
|
||||||
|
if not txi.amount:
|
||||||
|
raise signing.SigningError(
|
||||||
|
FailureType.DataError, "Expected input with amount"
|
||||||
|
)
|
||||||
|
self.segwit[i] = False
|
||||||
|
self.bip143_in += txi.amount
|
||||||
|
self.total_in += txi.amount
|
||||||
|
|
||||||
|
async def phase2_sign_nonsegwit_input(self, i_sign: int) -> None:
|
||||||
|
if self.coin.force_bip143 or self.coin.overwintered:
|
||||||
|
await self.phase2_sign_bip143_input(i_sign)
|
||||||
|
else:
|
||||||
|
await super().phase2_sign_nonsegwit_input(i_sign)
|
||||||
|
|
||||||
|
async def phase2_sign_bip143_input(self, i_sign) -> None:
|
||||||
|
# STAGE_REQUEST_SEGWIT_INPUT
|
||||||
|
txi_sign = await helpers.request_tx_input(self.tx_req, i_sign, self.coin)
|
||||||
|
self.input_check_wallet_path(txi_sign)
|
||||||
|
self.input_check_multisig_fingerprint(txi_sign)
|
||||||
|
|
||||||
|
is_bip143 = (
|
||||||
|
txi_sign.script_type == InputScriptType.SPENDADDRESS
|
||||||
|
or txi_sign.script_type == InputScriptType.SPENDMULTISIG
|
||||||
|
)
|
||||||
|
if not is_bip143 or txi_sign.amount > self.bip143_in:
|
||||||
|
raise signing.SigningError(
|
||||||
|
FailureType.ProcessError, "Transaction has changed during signing"
|
||||||
|
)
|
||||||
|
self.bip143_in -= txi_sign.amount
|
||||||
|
|
||||||
|
key_sign = self.keychain.derive(txi_sign.address_n, self.coin.curve_name)
|
||||||
|
key_sign_pub = key_sign.public_key()
|
||||||
|
self.hash143_hash = self.hash143.preimage_hash(
|
||||||
|
self.coin,
|
||||||
|
self.tx,
|
||||||
|
txi_sign,
|
||||||
|
addresses.ecdsa_hash_pubkey(key_sign_pub, self.coin),
|
||||||
|
self.get_hash_type(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# if multisig, check if signing with a key that is included in multisig
|
||||||
|
if txi_sign.multisig:
|
||||||
|
multisig.multisig_pubkey_index(txi_sign.multisig, key_sign_pub)
|
||||||
|
|
||||||
|
signature = signing.ecdsa_sign(key_sign, self.hash143_hash)
|
||||||
|
|
||||||
|
# serialize input with correct signature
|
||||||
|
gc.collect()
|
||||||
|
txi_sign.script_sig = self.input_derive_script(
|
||||||
|
txi_sign, key_sign_pub, signature
|
||||||
|
)
|
||||||
|
w_txi_sign = writers.empty_bytearray(
|
||||||
|
5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
|
||||||
|
)
|
||||||
|
if i_sign == 0: # serializing first input => prepend headers
|
||||||
|
self.write_sign_tx_header(w_txi_sign, True in self.segwit.values())
|
||||||
|
writers.write_tx_input(w_txi_sign, txi_sign)
|
||||||
|
self.tx_req.serialized = TxRequestSerializedType(i_sign, signature, w_txi_sign)
|
||||||
|
|
||||||
|
def on_negative_fee(self) -> None:
|
||||||
|
# some coins require negative fees for reward TX
|
||||||
|
if not self.coin.negative_fee:
|
||||||
|
super().on_negative_fee()
|
||||||
|
|
||||||
|
def get_raw_address(self, o: TxOutputType) -> bytes:
|
||||||
|
if self.coin.cashaddr_prefix is not None and o.address.startswith(
|
||||||
|
self.coin.cashaddr_prefix + ":"
|
||||||
|
):
|
||||||
|
prefix, addr = o.address.split(":")
|
||||||
|
version, data = cashaddr.decode(prefix, addr)
|
||||||
|
if version == cashaddr.ADDRESS_TYPE_P2KH:
|
||||||
|
version = self.coin.address_type
|
||||||
|
elif version == cashaddr.ADDRESS_TYPE_P2SH:
|
||||||
|
version = self.coin.address_type_p2sh
|
||||||
|
else:
|
||||||
|
raise signing.SigningError("Unknown cashaddr address type")
|
||||||
|
return bytes([version]) + data
|
||||||
|
else:
|
||||||
|
return super().get_raw_address(o)
|
||||||
|
|
||||||
|
def get_hash_type(self) -> int:
|
||||||
|
SIGHASH_FORKID = const(0x40)
|
||||||
|
hashtype = super().get_hash_type()
|
||||||
|
if self.coin.fork_id is not None:
|
||||||
|
hashtype |= (self.coin.fork_id << 8) | SIGHASH_FORKID
|
||||||
|
return hashtype
|
||||||
|
|
||||||
|
def write_sign_tx_footer(self, w: writers.Writer) -> None:
|
||||||
|
super().write_sign_tx_footer(w)
|
||||||
|
|
||||||
|
if self.coin.overwintered:
|
||||||
|
if self.tx.version == 3:
|
||||||
|
writers.write_uint32(w, self.tx.expiry) # expiryHeight
|
||||||
|
writers.write_varint(w, 0) # nJoinSplit
|
||||||
|
elif self.tx.version == 4:
|
||||||
|
writers.write_uint32(w, self.tx.expiry) # expiryHeight
|
||||||
|
writers.write_uint64(w, 0) # valueBalance
|
||||||
|
writers.write_varint(w, 0) # nShieldedSpend
|
||||||
|
writers.write_varint(w, 0) # nShieldedOutput
|
||||||
|
writers.write_varint(w, 0) # nJoinSplit
|
||||||
|
else:
|
||||||
|
raise signing.SigningError(
|
||||||
|
FailureType.DataError,
|
||||||
|
"Unsupported version for overwintered transaction",
|
||||||
|
)
|
||||||
|
|
||||||
|
def write_tx_header(
|
||||||
|
self, w: writers.Writer, tx: Union[SignTx, TransactionType], has_segwit: bool
|
||||||
|
) -> None:
|
||||||
|
if self.coin.overwintered:
|
||||||
|
# nVersion | fOverwintered
|
||||||
|
writers.write_uint32(w, tx.version | zcash.OVERWINTERED)
|
||||||
|
writers.write_uint32(w, tx.version_group_id) # nVersionGroupId
|
||||||
|
else:
|
||||||
|
writers.write_uint32(w, tx.version) # nVersion
|
||||||
|
if self.coin.timestamp:
|
||||||
|
writers.write_uint32(w, tx.timestamp)
|
||||||
|
if has_segwit:
|
||||||
|
writers.write_varint(w, 0x00) # segwit witness marker
|
||||||
|
writers.write_varint(w, 0x01) # segwit witness flag
|
||||||
|
|
||||||
|
async def write_prev_tx_footer(
|
||||||
|
self, w: writers.Writer, tx: TransactionType, prev_hash: bytes
|
||||||
|
) -> None:
|
||||||
|
await super().write_prev_tx_footer(w, tx, prev_hash)
|
||||||
|
|
||||||
|
if self.coin.extra_data:
|
||||||
|
ofs = 0
|
||||||
|
while ofs < tx.extra_data_len:
|
||||||
|
size = min(1024, tx.extra_data_len - ofs)
|
||||||
|
data = await helpers.request_tx_extra_data(
|
||||||
|
self.tx_req, ofs, size, prev_hash
|
||||||
|
)
|
||||||
|
writers.write_bytes_unchecked(w, data)
|
||||||
|
ofs += len(data)
|
@ -12,11 +12,7 @@ from trezor.utils import HashWriter
|
|||||||
|
|
||||||
from apps.common import coininfo, seed
|
from apps.common import coininfo, seed
|
||||||
from apps.wallet.sign_tx import addresses, helpers, multisig, progress, scripts, writers
|
from apps.wallet.sign_tx import addresses, helpers, multisig, progress, scripts, writers
|
||||||
from apps.wallet.sign_tx.signing import (
|
from apps.wallet.sign_tx.signing import Bitcoin, SigningError, ecdsa_sign
|
||||||
Bitcoin,
|
|
||||||
SigningError,
|
|
||||||
ecdsa_sign,
|
|
||||||
)
|
|
||||||
|
|
||||||
DECRED_SERIALIZE_FULL = const(0 << 16)
|
DECRED_SERIALIZE_FULL = const(0 << 16)
|
||||||
DECRED_SERIALIZE_NO_WITNESS = const(1 << 16)
|
DECRED_SERIALIZE_NO_WITNESS = const(1 << 16)
|
||||||
@ -76,7 +72,7 @@ class Decred(Bitcoin):
|
|||||||
await super().phase1_process_input(i, txi)
|
await super().phase1_process_input(i, txi)
|
||||||
w_txi = writers.empty_bytearray(8 if i == 0 else 0 + 9 + len(txi.prev_hash))
|
w_txi = writers.empty_bytearray(8 if i == 0 else 0 + 9 + len(txi.prev_hash))
|
||||||
if i == 0: # serializing first input => prepend headers
|
if i == 0: # serializing first input => prepend headers
|
||||||
self.write_tx_header(w_txi)
|
self.write_sign_tx_header(w_txi, False)
|
||||||
writers.write_tx_input_decred(w_txi, txi)
|
writers.write_tx_input_decred(w_txi, txi)
|
||||||
self.tx_req.serialized = TxRequestSerializedType(None, None, w_txi)
|
self.tx_req.serialized = TxRequestSerializedType(None, None, w_txi)
|
||||||
|
|
||||||
|
@ -2,11 +2,12 @@ import gc
|
|||||||
from micropython import const
|
from micropython import const
|
||||||
|
|
||||||
from trezor import utils
|
from trezor import utils
|
||||||
from trezor.crypto import base58, bip32, cashaddr, der
|
from trezor.crypto import base58, bip32, der
|
||||||
from trezor.crypto.curve import secp256k1
|
from trezor.crypto.curve import secp256k1
|
||||||
from trezor.crypto.hashlib import sha256
|
from trezor.crypto.hashlib import sha256
|
||||||
from trezor.messages import FailureType, InputScriptType, OutputScriptType
|
from trezor.messages import FailureType, InputScriptType, OutputScriptType
|
||||||
from trezor.messages.SignTx import SignTx
|
from trezor.messages.SignTx import SignTx
|
||||||
|
from trezor.messages.TransactionType import TransactionType
|
||||||
from trezor.messages.TxInputType import TxInputType
|
from trezor.messages.TxInputType import TxInputType
|
||||||
from trezor.messages.TxOutputBinType import TxOutputBinType
|
from trezor.messages.TxOutputBinType import TxOutputBinType
|
||||||
from trezor.messages.TxOutputType import TxOutputType
|
from trezor.messages.TxOutputType import TxOutputType
|
||||||
@ -26,11 +27,8 @@ from apps.wallet.sign_tx import (
|
|||||||
writers,
|
writers,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not utils.BITCOIN_ONLY:
|
|
||||||
from apps.wallet.sign_tx import zcash
|
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
# the number of bip32 levels used in a wallet (chain and address)
|
# the number of bip32 levels used in a wallet (chain and address)
|
||||||
_BIP32_WALLET_DEPTH = const(2)
|
_BIP32_WALLET_DEPTH = const(2)
|
||||||
@ -73,7 +71,9 @@ class Bitcoin:
|
|||||||
# - check that nothing changed
|
# - check that nothing changed
|
||||||
await self.phase2()
|
await self.phase2()
|
||||||
|
|
||||||
def initialize(self, tx: SignTx, keychain: seed.Keychain, coin: coininfo.CoinInfo) -> None:
|
def initialize(
|
||||||
|
self, tx: SignTx, keychain: seed.Keychain, coin: coininfo.CoinInfo
|
||||||
|
) -> None:
|
||||||
self.coin = coin
|
self.coin = coin
|
||||||
self.tx = helpers.sanitize_sign_tx(tx, self.coin)
|
self.tx = helpers.sanitize_sign_tx(tx, self.coin)
|
||||||
self.keychain = keychain
|
self.keychain = keychain
|
||||||
@ -81,9 +81,13 @@ class Bitcoin:
|
|||||||
self.multisig_fp = (
|
self.multisig_fp = (
|
||||||
multisig.MultisigFingerprint()
|
multisig.MultisigFingerprint()
|
||||||
) # control checksum of multisig inputs
|
) # control checksum of multisig inputs
|
||||||
self.wallet_path = [] # type: Optional[List[int]] # common prefix of input paths
|
self.wallet_path = (
|
||||||
|
[]
|
||||||
|
) # type: Optional[List[int]] # common prefix of input paths
|
||||||
self.bip143_in = 0 # sum of segwit input amounts
|
self.bip143_in = 0 # sum of segwit input amounts
|
||||||
self.segwit = {} # type: Dict[int, bool] # dict of booleans stating if input is segwit
|
self.segwit = (
|
||||||
|
{}
|
||||||
|
) # type: Dict[int, bool] # dict of booleans stating if input is segwit
|
||||||
self.total_in = 0 # sum of input amounts
|
self.total_in = 0 # sum of input amounts
|
||||||
self.total_out = 0 # sum of output amounts
|
self.total_out = 0 # sum of output amounts
|
||||||
self.change_out = 0 # change output amount
|
self.change_out = 0 # change output amount
|
||||||
@ -99,20 +103,7 @@ class Bitcoin:
|
|||||||
self.init_hash143()
|
self.init_hash143()
|
||||||
|
|
||||||
def init_hash143(self) -> None:
|
def init_hash143(self) -> None:
|
||||||
if not utils.BITCOIN_ONLY and self.coin.overwintered:
|
self.hash143 = segwit_bip143.Bip143() # BIP-0143 transaction hashing
|
||||||
if self.tx.version == 3:
|
|
||||||
branch_id = self.tx.branch_id or 0x5BA81B19 # Overwinter
|
|
||||||
self.hash143 = zcash.Zip143(branch_id) # ZIP-0143 transaction hashing
|
|
||||||
elif self.tx.version == 4:
|
|
||||||
branch_id = self.tx.branch_id or 0x76B809BB # Sapling
|
|
||||||
self.hash143 = zcash.Zip243(branch_id) # ZIP-0243 transaction hashing
|
|
||||||
else:
|
|
||||||
raise SigningError(
|
|
||||||
FailureType.DataError,
|
|
||||||
"Unsupported version for overwintered transaction",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.hash143 = segwit_bip143.Bip143() # BIP-0143 transaction hashing
|
|
||||||
|
|
||||||
async def phase1(self) -> None:
|
async def phase1(self) -> None:
|
||||||
weight = tx_weight.TxWeightCalculator(
|
weight = tx_weight.TxWeightCalculator(
|
||||||
@ -174,37 +165,28 @@ class Bitcoin:
|
|||||||
InputScriptType.SPENDWITNESS,
|
InputScriptType.SPENDWITNESS,
|
||||||
InputScriptType.SPENDP2SHWITNESS,
|
InputScriptType.SPENDP2SHWITNESS,
|
||||||
):
|
):
|
||||||
if not self.coin.segwit:
|
await self.phase1_process_segwit_input(i, txi)
|
||||||
raise SigningError(
|
|
||||||
FailureType.DataError, "Segwit not enabled on this coin"
|
|
||||||
)
|
|
||||||
if not txi.amount:
|
|
||||||
raise SigningError(FailureType.DataError, "Segwit input without amount")
|
|
||||||
self.segwit[i] = True
|
|
||||||
self.bip143_in += txi.amount
|
|
||||||
self.total_in += txi.amount
|
|
||||||
elif txi.script_type in (
|
elif txi.script_type in (
|
||||||
InputScriptType.SPENDADDRESS,
|
InputScriptType.SPENDADDRESS,
|
||||||
InputScriptType.SPENDMULTISIG,
|
InputScriptType.SPENDMULTISIG,
|
||||||
):
|
):
|
||||||
if not utils.BITCOIN_ONLY and (
|
await self.phase1_process_nonsegwit_input(i, txi)
|
||||||
self.coin.force_bip143 or self.coin.overwintered
|
|
||||||
):
|
|
||||||
if not txi.amount:
|
|
||||||
raise SigningError(
|
|
||||||
FailureType.DataError, "Expected input with amount"
|
|
||||||
)
|
|
||||||
self.segwit[i] = False
|
|
||||||
self.bip143_in += txi.amount
|
|
||||||
self.total_in += txi.amount
|
|
||||||
else:
|
|
||||||
self.segwit[i] = False
|
|
||||||
self.total_in += await self.get_prevtx_output_value(
|
|
||||||
txi.prev_hash, txi.prev_index
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise SigningError(FailureType.DataError, "Wrong input script type")
|
raise SigningError(FailureType.DataError, "Wrong input script type")
|
||||||
|
|
||||||
|
async def phase1_process_segwit_input(self, i: int, txi: TxInputType) -> None:
|
||||||
|
if not txi.amount:
|
||||||
|
raise SigningError(FailureType.DataError, "Segwit input without amount")
|
||||||
|
self.segwit[i] = True
|
||||||
|
self.bip143_in += txi.amount
|
||||||
|
self.total_in += txi.amount
|
||||||
|
|
||||||
|
async def phase1_process_nonsegwit_input(self, i: int, txi: TxInputType) -> None:
|
||||||
|
self.segwit[i] = False
|
||||||
|
self.total_in += await self.get_prevtx_output_value(
|
||||||
|
txi.prev_hash, txi.prev_index
|
||||||
|
)
|
||||||
|
|
||||||
async def phase1_confirm_output(
|
async def phase1_confirm_output(
|
||||||
self, i: int, txo: TxOutputType, txo_bin: TxOutputBinType
|
self, i: int, txo: TxOutputType, txo_bin: TxOutputBinType
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -218,10 +200,8 @@ class Bitcoin:
|
|||||||
self.hash143.add_output(txo_bin)
|
self.hash143.add_output(txo_bin)
|
||||||
self.total_out += txo_bin.amount
|
self.total_out += txo_bin.amount
|
||||||
|
|
||||||
def on_negative_fee(self):
|
def on_negative_fee(self) -> None:
|
||||||
# some coins require negative fees for reward TX
|
raise SigningError(FailureType.NotEnoughFunds, "Not enough funds")
|
||||||
if utils.BITCOIN_ONLY or not self.coin.negative_fee:
|
|
||||||
raise SigningError(FailureType.NotEnoughFunds, "Not enough funds")
|
|
||||||
|
|
||||||
async def phase2(self) -> None:
|
async def phase2(self) -> None:
|
||||||
self.tx_req.serialized = None
|
self.tx_req.serialized = None
|
||||||
@ -231,12 +211,8 @@ class Bitcoin:
|
|||||||
progress.advance()
|
progress.advance()
|
||||||
if self.segwit[i]:
|
if self.segwit[i]:
|
||||||
await self.phase2_serialize_segwit_input(i)
|
await self.phase2_serialize_segwit_input(i)
|
||||||
elif not utils.BITCOIN_ONLY and (
|
|
||||||
self.coin.force_bip143 or self.coin.overwintered
|
|
||||||
):
|
|
||||||
await self.phase2_sign_bip143_input(i)
|
|
||||||
else:
|
else:
|
||||||
await self.phase2_sign_legacy_input(i)
|
await self.phase2_sign_nonsegwit_input(i)
|
||||||
|
|
||||||
# Serialize outputs.
|
# Serialize outputs.
|
||||||
tx_ser = TxRequestSerializedType()
|
tx_ser = TxRequestSerializedType()
|
||||||
@ -257,6 +233,7 @@ class Bitcoin:
|
|||||||
tx_ser.signature_index = i
|
tx_ser.signature_index = i
|
||||||
tx_ser.signature = signature
|
tx_ser.signature = signature
|
||||||
elif any_segwit:
|
elif any_segwit:
|
||||||
|
# TODO what if a non-segwit input follows after a segwit input?
|
||||||
tx_ser.serialized_tx += bytearray(
|
tx_ser.serialized_tx += bytearray(
|
||||||
1
|
1
|
||||||
) # empty witness for non-segwit inputs
|
) # empty witness for non-segwit inputs
|
||||||
@ -265,27 +242,7 @@ class Bitcoin:
|
|||||||
|
|
||||||
self.tx_req.serialized = tx_ser
|
self.tx_req.serialized = tx_ser
|
||||||
|
|
||||||
writers.write_uint32(tx_ser.serialized_tx, self.tx.lock_time)
|
self.write_sign_tx_footer(tx_ser.serialized_tx)
|
||||||
|
|
||||||
if not utils.BITCOIN_ONLY and self.coin.overwintered:
|
|
||||||
if self.tx.version == 3:
|
|
||||||
writers.write_uint32(
|
|
||||||
tx_ser.serialized_tx, self.tx.expiry
|
|
||||||
) # expiryHeight
|
|
||||||
writers.write_varint(tx_ser.serialized_tx, 0) # nJoinSplit
|
|
||||||
elif self.tx.version == 4:
|
|
||||||
writers.write_uint32(
|
|
||||||
tx_ser.serialized_tx, self.tx.expiry
|
|
||||||
) # expiryHeight
|
|
||||||
writers.write_uint64(tx_ser.serialized_tx, 0) # valueBalance
|
|
||||||
writers.write_varint(tx_ser.serialized_tx, 0) # nShieldedSpend
|
|
||||||
writers.write_varint(tx_ser.serialized_tx, 0) # nShieldedOutput
|
|
||||||
writers.write_varint(tx_ser.serialized_tx, 0) # nJoinSplit
|
|
||||||
else:
|
|
||||||
raise SigningError(
|
|
||||||
FailureType.DataError,
|
|
||||||
"Unsupported version for overwintered transaction",
|
|
||||||
)
|
|
||||||
|
|
||||||
await helpers.request_tx_finish(self.tx_req)
|
await helpers.request_tx_finish(self.tx_req)
|
||||||
|
|
||||||
@ -309,7 +266,7 @@ class Bitcoin:
|
|||||||
7 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
|
7 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
|
||||||
)
|
)
|
||||||
if i_sign == 0: # serializing first input => prepend headers
|
if i_sign == 0: # serializing first input => prepend headers
|
||||||
self.write_tx_header(w_txi)
|
self.write_sign_tx_header(w_txi, True)
|
||||||
writers.write_tx_input(w_txi, txi_sign)
|
writers.write_tx_input(w_txi, txi_sign)
|
||||||
self.tx_req.serialized = TxRequestSerializedType(serialized_tx=w_txi)
|
self.tx_req.serialized = TxRequestSerializedType(serialized_tx=w_txi)
|
||||||
|
|
||||||
@ -349,62 +306,13 @@ class Bitcoin:
|
|||||||
|
|
||||||
return witness, signature
|
return witness, signature
|
||||||
|
|
||||||
async def phase2_sign_bip143_input(self, i_sign) -> None:
|
async def phase2_sign_nonsegwit_input(self, i_sign: int) -> None:
|
||||||
# STAGE_REQUEST_SEGWIT_INPUT
|
|
||||||
txi_sign = await helpers.request_tx_input(self.tx_req, i_sign, self.coin)
|
|
||||||
self.input_check_wallet_path(txi_sign)
|
|
||||||
self.input_check_multisig_fingerprint(txi_sign)
|
|
||||||
|
|
||||||
is_bip143 = (
|
|
||||||
txi_sign.script_type == InputScriptType.SPENDADDRESS
|
|
||||||
or txi_sign.script_type == InputScriptType.SPENDMULTISIG
|
|
||||||
)
|
|
||||||
if not is_bip143 or txi_sign.amount > self.bip143_in:
|
|
||||||
raise SigningError(
|
|
||||||
FailureType.ProcessError, "Transaction has changed during signing"
|
|
||||||
)
|
|
||||||
self.bip143_in -= txi_sign.amount
|
|
||||||
|
|
||||||
key_sign = self.keychain.derive(txi_sign.address_n, self.coin.curve_name)
|
|
||||||
key_sign_pub = key_sign.public_key()
|
|
||||||
self.hash143_hash = self.hash143.preimage_hash(
|
|
||||||
self.coin,
|
|
||||||
self.tx,
|
|
||||||
txi_sign,
|
|
||||||
addresses.ecdsa_hash_pubkey(key_sign_pub, self.coin),
|
|
||||||
self.get_hash_type(),
|
|
||||||
)
|
|
||||||
|
|
||||||
# if multisig, check if signing with a key that is included in multisig
|
|
||||||
if txi_sign.multisig:
|
|
||||||
multisig.multisig_pubkey_index(txi_sign.multisig, key_sign_pub)
|
|
||||||
|
|
||||||
signature = ecdsa_sign(key_sign, self.hash143_hash)
|
|
||||||
|
|
||||||
# serialize input with correct signature
|
|
||||||
gc.collect()
|
|
||||||
txi_sign.script_sig = self.input_derive_script(
|
|
||||||
txi_sign, key_sign_pub, signature
|
|
||||||
)
|
|
||||||
w_txi_sign = writers.empty_bytearray(
|
|
||||||
5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
|
|
||||||
)
|
|
||||||
if i_sign == 0: # serializing first input => prepend headers
|
|
||||||
self.write_tx_header(w_txi_sign)
|
|
||||||
writers.write_tx_input(w_txi_sign, txi_sign)
|
|
||||||
self.tx_req.serialized = TxRequestSerializedType(i_sign, signature, w_txi_sign)
|
|
||||||
|
|
||||||
async def phase2_sign_legacy_input(self, i_sign) -> None:
|
|
||||||
# hash of what we are signing with this input
|
# hash of what we are signing with this input
|
||||||
h_sign = utils.HashWriter(sha256())
|
h_sign = utils.HashWriter(sha256())
|
||||||
# same as h_first, checked before signing the digest
|
# same as h_first, checked before signing the digest
|
||||||
h_second = utils.HashWriter(sha256())
|
h_second = utils.HashWriter(sha256())
|
||||||
|
|
||||||
writers.write_uint32(h_sign, self.tx.version) # nVersion
|
self.write_sign_tx_header(h_sign, has_segwit=False)
|
||||||
if not utils.BITCOIN_ONLY and self.coin.timestamp:
|
|
||||||
writers.write_uint32(h_sign, self.tx.timestamp)
|
|
||||||
|
|
||||||
writers.write_varint(h_sign, self.tx.inputs_count)
|
|
||||||
|
|
||||||
for i in range(self.tx.inputs_count):
|
for i in range(self.tx.inputs_count):
|
||||||
# STAGE_REQUEST_4_INPUT
|
# STAGE_REQUEST_4_INPUT
|
||||||
@ -473,7 +381,7 @@ class Bitcoin:
|
|||||||
5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
|
5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
|
||||||
)
|
)
|
||||||
if i_sign == 0: # serializing first input => prepend headers
|
if i_sign == 0: # serializing first input => prepend headers
|
||||||
self.write_tx_header(w_txi_sign)
|
self.write_sign_tx_header(w_txi_sign, True in self.segwit.values())
|
||||||
writers.write_tx_input(w_txi_sign, txi_sign)
|
writers.write_tx_input(w_txi_sign, txi_sign)
|
||||||
self.tx_req.serialized = TxRequestSerializedType(i_sign, signature, w_txi_sign)
|
self.tx_req.serialized = TxRequestSerializedType(i_sign, signature, w_txi_sign)
|
||||||
|
|
||||||
@ -492,7 +400,7 @@ class Bitcoin:
|
|||||||
return w_txo_bin
|
return w_txo_bin
|
||||||
|
|
||||||
async def get_prevtx_output_value(self, prev_hash: bytes, prev_index: int) -> int:
|
async def get_prevtx_output_value(self, prev_hash: bytes, prev_index: int) -> int:
|
||||||
total_out = 0 # sum of output amounts
|
amount_out = 0 # sum of output amounts
|
||||||
|
|
||||||
# STAGE_REQUEST_2_PREV_META
|
# STAGE_REQUEST_2_PREV_META
|
||||||
tx = await helpers.request_tx_meta(self.tx_req, self.coin, prev_hash)
|
tx = await helpers.request_tx_meta(self.tx_req, self.coin, prev_hash)
|
||||||
@ -504,16 +412,8 @@ class Bitcoin:
|
|||||||
|
|
||||||
txh = utils.HashWriter(sha256())
|
txh = utils.HashWriter(sha256())
|
||||||
|
|
||||||
if not utils.BITCOIN_ONLY and self.coin.overwintered:
|
# TODO set has_segwit correctly
|
||||||
writers.write_uint32(
|
self.write_tx_header(txh, tx, has_segwit=False)
|
||||||
txh, tx.version | zcash.OVERWINTERED
|
|
||||||
) # nVersion | fOverwintered
|
|
||||||
writers.write_uint32(txh, tx.version_group_id) # nVersionGroupId
|
|
||||||
else:
|
|
||||||
writers.write_uint32(txh, tx.version) # nVersion
|
|
||||||
if not utils.BITCOIN_ONLY and self.coin.timestamp:
|
|
||||||
writers.write_uint32(txh, tx.timestamp)
|
|
||||||
|
|
||||||
writers.write_varint(txh, tx.inputs_cnt)
|
writers.write_varint(txh, tx.inputs_cnt)
|
||||||
|
|
||||||
for i in range(tx.inputs_cnt):
|
for i in range(tx.inputs_cnt):
|
||||||
@ -530,19 +430,9 @@ class Bitcoin:
|
|||||||
)
|
)
|
||||||
writers.write_tx_output(txh, txo_bin)
|
writers.write_tx_output(txh, txo_bin)
|
||||||
if o == prev_index:
|
if o == prev_index:
|
||||||
total_out += txo_bin.amount
|
amount_out = txo_bin.amount
|
||||||
|
|
||||||
writers.write_uint32(txh, tx.lock_time)
|
await self.write_prev_tx_footer(txh, tx, prev_hash)
|
||||||
|
|
||||||
if not utils.BITCOIN_ONLY and self.coin.extra_data:
|
|
||||||
ofs = 0
|
|
||||||
while ofs < tx.extra_data_len:
|
|
||||||
size = min(1024, tx.extra_data_len - ofs)
|
|
||||||
data = await helpers.request_tx_extra_data(
|
|
||||||
self.tx_req, ofs, size, prev_hash
|
|
||||||
)
|
|
||||||
writers.write_bytes_unchecked(txh, data)
|
|
||||||
ofs += len(data)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
writers.get_tx_hash(txh, double=self.coin.sign_hash_double, reverse=True)
|
writers.get_tx_hash(txh, double=self.coin.sign_hash_double, reverse=True)
|
||||||
@ -552,32 +442,34 @@ class Bitcoin:
|
|||||||
FailureType.ProcessError, "Encountered invalid prev_hash"
|
FailureType.ProcessError, "Encountered invalid prev_hash"
|
||||||
)
|
)
|
||||||
|
|
||||||
return total_out
|
return amount_out
|
||||||
|
|
||||||
# TX Helpers
|
# TX Helpers
|
||||||
# ===
|
# ===
|
||||||
|
|
||||||
def get_hash_type(self) -> int:
|
def get_hash_type(self) -> int:
|
||||||
SIGHASH_FORKID = const(0x40)
|
|
||||||
SIGHASH_ALL = const(0x01)
|
SIGHASH_ALL = const(0x01)
|
||||||
hashtype = SIGHASH_ALL
|
return SIGHASH_ALL
|
||||||
if self.coin.fork_id is not None:
|
|
||||||
hashtype |= (self.coin.fork_id << 8) | SIGHASH_FORKID
|
|
||||||
return hashtype
|
|
||||||
|
|
||||||
def write_tx_header(self, w: writers.Writer) -> None:
|
def write_sign_tx_header(self, w: writers.Writer, has_segwit: bool) -> None:
|
||||||
if not utils.BITCOIN_ONLY and self.coin.overwintered:
|
self.write_tx_header(w, self.tx, has_segwit)
|
||||||
# nVersion | fOverwintered
|
writers.write_varint(w, self.tx.inputs_count)
|
||||||
writers.write_uint32(w, self.tx.version | zcash.OVERWINTERED)
|
|
||||||
writers.write_uint32(w, self.tx.version_group_id) # nVersionGroupId
|
def write_sign_tx_footer(self, w: writers.Writer) -> None:
|
||||||
else:
|
writers.write_uint32(w, self.tx.lock_time)
|
||||||
writers.write_uint32(w, self.tx.version) # nVersion
|
|
||||||
if not utils.BITCOIN_ONLY and self.coin.timestamp:
|
def write_tx_header(
|
||||||
writers.write_uint32(w, self.tx.timestamp)
|
self, w: writers.Writer, tx: Union[SignTx, TransactionType], has_segwit: bool
|
||||||
if True in self.segwit.values():
|
) -> None:
|
||||||
|
writers.write_uint32(w, tx.version) # nVersion
|
||||||
|
if has_segwit:
|
||||||
writers.write_varint(w, 0x00) # segwit witness marker
|
writers.write_varint(w, 0x00) # segwit witness marker
|
||||||
writers.write_varint(w, 0x01) # segwit witness flag
|
writers.write_varint(w, 0x01) # segwit witness flag
|
||||||
writers.write_varint(w, self.tx.inputs_count)
|
|
||||||
|
async def write_prev_tx_footer(
|
||||||
|
self, w: writers.Writer, tx: TransactionType, prev_hash: bytes
|
||||||
|
) -> None:
|
||||||
|
writers.write_uint32(w, tx.lock_time)
|
||||||
|
|
||||||
# TX Outputs
|
# TX Outputs
|
||||||
# ===
|
# ===
|
||||||
@ -597,25 +489,7 @@ class Bitcoin:
|
|||||||
)
|
)
|
||||||
return scripts.output_script_native_p2wpkh_or_p2wsh(witprog)
|
return scripts.output_script_native_p2wpkh_or_p2wsh(witprog)
|
||||||
|
|
||||||
if (
|
raw_address = self.get_raw_address(o)
|
||||||
not utils.BITCOIN_ONLY
|
|
||||||
and self.coin.cashaddr_prefix is not None
|
|
||||||
and o.address.startswith(self.coin.cashaddr_prefix + ":")
|
|
||||||
):
|
|
||||||
prefix, addr = o.address.split(":")
|
|
||||||
version, data = cashaddr.decode(prefix, addr)
|
|
||||||
if version == cashaddr.ADDRESS_TYPE_P2KH:
|
|
||||||
version = self.coin.address_type
|
|
||||||
elif version == cashaddr.ADDRESS_TYPE_P2SH:
|
|
||||||
version = self.coin.address_type_p2sh
|
|
||||||
else:
|
|
||||||
raise SigningError("Unknown cashaddr address type")
|
|
||||||
raw_address = bytes([version]) + data
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
raw_address = base58.decode_check(o.address, self.coin.b58_hash)
|
|
||||||
except ValueError:
|
|
||||||
raise SigningError(FailureType.DataError, "Invalid address")
|
|
||||||
|
|
||||||
if address_type.check(self.coin.address_type, raw_address):
|
if address_type.check(self.coin.address_type, raw_address):
|
||||||
# p2pkh
|
# p2pkh
|
||||||
@ -631,6 +505,12 @@ class Bitcoin:
|
|||||||
|
|
||||||
raise SigningError(FailureType.DataError, "Invalid address type")
|
raise SigningError(FailureType.DataError, "Invalid address type")
|
||||||
|
|
||||||
|
def get_raw_address(self, o: TxOutputType) -> bytes:
|
||||||
|
try:
|
||||||
|
return base58.decode_check(o.address, self.coin.b58_hash)
|
||||||
|
except ValueError:
|
||||||
|
raise SigningError(FailureType.DataError, "Invalid address")
|
||||||
|
|
||||||
def get_address_for_change(self, o: TxOutputType) -> str:
|
def get_address_for_change(self, o: TxOutputType) -> str:
|
||||||
try:
|
try:
|
||||||
input_script_type = helpers.CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES[
|
input_script_type = helpers.CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES[
|
||||||
|
Loading…
Reference in New Issue
Block a user