88 lines
3.1 KiB
Python
Executable file
88 lines
3.1 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
#
|
|
# MMGen Wallet, a terminal-based cryptocurrency wallet
|
|
# Copyright (C)2013-2026 The MMGen Project <mmgen@tuta.io>
|
|
# Licensed under the GNU General Public License, Version 3:
|
|
# https://www.gnu.org/licenses
|
|
# Public project repositories:
|
|
# https://github.com/mmgen/mmgen-wallet
|
|
# https://gitlab.com/mmgen/mmgen-wallet
|
|
|
|
"""
|
|
proto.btc.tx.completed: Bitcoin completed transaction class
|
|
"""
|
|
|
|
from ....tx import completed as TxBase
|
|
from ....obj import HexStr
|
|
from ....util import msg, die
|
|
from .base import Base, decodeScriptPubKey
|
|
|
|
class Completed(Base, TxBase.Completed):
|
|
fn_fee_unit = 'satoshi'
|
|
|
|
def get_swap_memo_maybe(self):
|
|
if o := self.data_output:
|
|
return o.data
|
|
|
|
# check signature and witness data
|
|
def check_sigs(self): # return True if sigs found, False otherwise; raise exception on error
|
|
txins = self.deserialized.txins
|
|
has_ss = any(ti['scriptSig'] for ti in txins)
|
|
has_witness = any('witness' in ti and ti['witness'] for ti in txins)
|
|
if not (has_ss or has_witness):
|
|
return False
|
|
fs = "Hex TX has {} scriptSig but input is of type '{}'!"
|
|
for n, ti in enumerate(txins):
|
|
mmti = self.inputs[n]
|
|
if ti['scriptSig'] == '' or (len(ti['scriptSig']) == 46 and # native P2WPKH or P2SH-P2WPKH
|
|
ti['scriptSig'][:6] == '16' + self.proto.witness_vernum_hex + '14'):
|
|
assert 'witness' in ti, 'missing witness'
|
|
assert isinstance(ti['witness'], list) and len(ti['witness']) == 2, 'malformed witness'
|
|
assert len(ti['witness'][1]) == 66, 'incorrect witness pubkey length'
|
|
assert mmti.mmtype == ('S', 'B')[ti['scriptSig']==''], fs.format('witness-type', mmti.mmtype)
|
|
else: # non-witness
|
|
assert mmti.mmtype not in ('S', 'B'), fs.format('signature in', mmti.mmtype)
|
|
assert not 'witness' in ti, 'non-witness input has witness'
|
|
# sig_size 72 (DER format), pubkey_size 'compressed':33, 'uncompressed':65
|
|
assert (200 < len(ti['scriptSig']) < 300), 'malformed scriptSig' # VERY rough check
|
|
return True
|
|
|
|
def check_pubkey_scripts(self):
|
|
for n, i in enumerate(self.inputs, 1):
|
|
ds = decodeScriptPubKey(self.proto, i.scriptPubKey)
|
|
if ds.addr != i.addr:
|
|
if ds.addr_fmt != i.addr.addr_fmt:
|
|
m = 'Address format of scriptPubKey ({}) does not match that of address ({}) in input #{}'
|
|
msg(m.format(ds.addr_fmt, i.addr.addr_fmt, n))
|
|
m = 'ERROR: Address and scriptPubKey of transaction input #{} do not match!'
|
|
die(3, (m+'\n {:23}{}'*3).format(
|
|
n,
|
|
'address:', i.addr,
|
|
'scriptPubKey:', i.scriptPubKey,
|
|
'scriptPubKey->address:', ds.addr))
|
|
|
|
def is_replaceable(self):
|
|
return self.inputs[0].sequence == self.proto.max_int - 2
|
|
|
|
@property
|
|
def send_amt(self):
|
|
return self.sum_outputs(
|
|
exclude = None if len(self.nondata_outputs) == 1 else self.chg_idx
|
|
)
|
|
|
|
def check_txfile_hex_data(self):
|
|
self.serialized = HexStr(self.serialized)
|
|
|
|
def parse_txfile_serialized_data(self):
|
|
pass
|
|
|
|
@property
|
|
def fee(self):
|
|
return self.sum_inputs() - self.sum_outputs()
|
|
|
|
@property
|
|
def change(self):
|
|
return self.sum_outputs() - self.send_amt
|
|
|
|
def get_serialized_locktime(self):
|
|
return int(bytes.fromhex(self.serialized[-8:])[::-1].hex(), 16)
|