def hash143_preimage_hash( self, txi: TxInputType, public_keys: List[bytes], threshold: int ) -> bytes: h_preimage = HashWriter(sha256()) # nVersion writers.write_uint32(h_preimage, self.tx.version) # hashPrevouts prevouts_hash = writers.get_tx_hash( self.h_prevouts, double=self.coin.sign_hash_double ) writers.write_bytes_fixed(h_preimage, prevouts_hash, writers.TX_HASH_SIZE) # hashSequence sequence_hash = writers.get_tx_hash( self.h_sequence, double=self.coin.sign_hash_double ) writers.write_bytes_fixed(h_preimage, sequence_hash, writers.TX_HASH_SIZE) # outpoint writers.write_bytes_reversed(h_preimage, txi.prev_hash, writers.TX_HASH_SIZE) writers.write_uint32(h_preimage, txi.prev_index) # scriptCode script_code = scripts.bip143_derive_script_code( txi, public_keys, threshold, self.coin ) writers.write_bytes_prefixed(h_preimage, script_code) # amount writers.write_uint64(h_preimage, txi.amount) # nSequence writers.write_uint32(h_preimage, txi.sequence) # hashOutputs outputs_hash = writers.get_tx_hash( self.h_outputs, double=self.coin.sign_hash_double ) writers.write_bytes_fixed(h_preimage, outputs_hash, writers.TX_HASH_SIZE) # nLockTime writers.write_uint32(h_preimage, self.tx.lock_time) # nHashType writers.write_uint32(h_preimage, self.get_sighash_type(txi)) return writers.get_tx_hash(h_preimage, double=self.coin.sign_hash_double)
async def get_prevtx_output_value(coin: CoinInfo, tx_req: TxRequest, prev_hash: bytes, prev_index: int) -> int: total_out = 0 # sum of output amounts # STAGE_REQUEST_2_PREV_META tx = await request_tx_meta(tx_req, prev_hash) txh = HashWriter(sha256) if tx.overwintered: write_uint32(txh, tx.version | OVERWINTERED) # nVersion | fOverwintered write_uint32(txh, coin.version_group_id) # nVersionGroupId else: write_uint32(txh, tx.version) # nVersion write_varint(txh, tx.inputs_cnt) for i in range(tx.inputs_cnt): # STAGE_REQUEST_2_PREV_INPUT txi = await request_tx_input(tx_req, i, prev_hash) write_tx_input(txh, txi) write_varint(txh, tx.outputs_cnt) for o in range(tx.outputs_cnt): # STAGE_REQUEST_2_PREV_OUTPUT txo_bin = await request_tx_output(tx_req, o, prev_hash) write_tx_output(txh, txo_bin) if o == prev_index: total_out += txo_bin.amount write_uint32(txh, tx.lock_time) if tx.overwintered: write_uint32(txh, tx.expiry) ofs = 0 while ofs < tx.extra_data_len: size = min(1024, tx.extra_data_len - ofs) data = await request_tx_extra_data(tx_req, ofs, size, prev_hash) write_bytes(txh, data) ofs += len(data) if get_tx_hash(txh, double=True, reverse=True) != prev_hash: raise SigningError(FailureType.ProcessError, 'Encountered invalid prev_hash') return total_out
async def sign_tx(ctx: wire.Context, msg: EosSignTx, keychain: Keychain) -> EosSignedTx: if msg.chain_id is None: raise wire.DataError("No chain id") if msg.header is None: raise wire.DataError("No header") if msg.num_actions is None or msg.num_actions == 0: raise wire.DataError("No actions") await paths.validate_path(ctx, validate_full_path, keychain, msg.address_n, CURVE) node = keychain.derive(msg.address_n) sha = HashWriter(sha256()) await _init(ctx, sha, msg) await _actions(ctx, sha, msg.num_actions) writers.write_variant32(sha, 0) writers.write_bytes_unchecked(sha, bytearray(32)) digest = sha.get_digest() signature = secp256k1.sign(node.private_key(), digest, True, secp256k1.CANONICAL_SIG_EOS) return EosSignedTx(signature=base58_encode("SIG_", "K1", signature))
async def step1_process_inputs(self) -> None: h_external_inputs_check = HashWriter(sha256()) for i in range(self.tx_info.tx.inputs_count): # STAGE_REQUEST_1_INPUT in legacy txi = await helpers.request_tx_input(self.tx_req, i, self.coin) script_pubkey = self.input_derive_script(txi) self.tx_info.add_input(txi, script_pubkey) if txi.script_type not in ( InputScriptType.SPENDTAPROOT, InputScriptType.EXTERNAL, ): self.taproot_only = False if input_is_segwit(txi): self.segwit.add(i) if input_is_external(txi): self.external.add(i) writers.write_tx_input_check(h_external_inputs_check, txi) await self.process_external_input(txi) else: await self.process_internal_input(txi) if txi.orig_hash: await self.process_original_input(txi, script_pubkey) self.h_inputs = self.tx_info.get_tx_check_digest() self.h_external_inputs = h_external_inputs_check.get_digest() # Finalize original inputs. for orig in self.orig_txs: if orig.index != orig.tx.inputs_count: raise wire.ProcessError("Removal of original inputs is not supported.") orig.index = 0 # Reset counter for outputs.
def preimage_hash( self, coin: CoinInfo, tx: SignTx, txi: TxInputType, pubkeyhash: bytes, sighash: int, ) -> bytes: h_preimage = HashWriter( blake2b(outlen=32, personal=b"ZcashSigHash" + struct.pack("<I", self.branch_id))) ensure(coin.overwintered) ensure(tx.version == 3) write_uint32(h_preimage, tx.version | OVERWINTERED) # 1. nVersion | fOverwintered write_uint32(h_preimage, tx.version_group_id) # 2. nVersionGroupId # 3. hashPrevouts write_bytes_fixed(h_preimage, bytearray(self.get_prevouts_hash()), TX_HASH_SIZE) # 4. hashSequence write_bytes_fixed(h_preimage, bytearray(self.get_sequence_hash()), TX_HASH_SIZE) # 5. hashOutputs write_bytes_fixed(h_preimage, bytearray(self.get_outputs_hash()), TX_HASH_SIZE) # 6. hashJoinSplits write_bytes_fixed(h_preimage, b"\x00" * TX_HASH_SIZE, TX_HASH_SIZE) write_uint32(h_preimage, tx.lock_time) # 7. nLockTime write_uint32(h_preimage, tx.expiry) # 8. expiryHeight write_uint32(h_preimage, sighash) # 9. nHashType write_bytes_reversed(h_preimage, txi.prev_hash, TX_HASH_SIZE) # 10a. outpoint write_uint32(h_preimage, txi.prev_index) script_code = derive_script_code(txi, pubkeyhash) # 10b. scriptCode write_bytes_prefixed(h_preimage, script_code) write_uint64(h_preimage, txi.amount) # 10c. value write_uint32(h_preimage, txi.sequence) # 10d. nSequence return get_tx_hash(h_preimage)
def preimage_hash( self, coin: CoinInfo, tx: SignTx, txi: TxInputType, pubkeyhash: bytes, sighash: int, ) -> bytes: h_preimage = HashWriter( blake2b(outlen=32, personal=b"ZcashSigHash\xbb\x09\xb8\x76") ) # BRANCH_ID = 0x76b809bb / Sapling ensure(tx.overwintered) ensure(tx.version == 4) write_uint32(h_preimage, tx.version | OVERWINTERED) # 1. nVersion | fOverwintered write_uint32(h_preimage, tx.version_group_id) # 2. nVersionGroupId write_bytes(h_preimage, bytearray(self.get_prevouts_hash())) # 3. hashPrevouts write_bytes(h_preimage, bytearray(self.get_sequence_hash())) # 4. hashSequence write_bytes(h_preimage, bytearray(self.get_outputs_hash())) # 5. hashOutputs write_bytes(h_preimage, b"\x00" * 32) # 6. hashJoinSplits write_bytes(h_preimage, b"\x00" * 32) # 7. hashShieldedSpends write_bytes(h_preimage, b"\x00" * 32) # 8. hashShieldedOutputs write_uint32(h_preimage, tx.lock_time - self.hash_lock_offset) # 9. nLockTime write_uint32(h_preimage, tx.expiry) # 10. expiryHeight write_uint64(h_preimage, 0) # 11. valueBalance write_uint32(h_preimage, sighash) # 12. nHashType write_bytes_reversed(h_preimage, txi.prev_hash) # 13a. outpoint write_uint32(h_preimage, txi.prev_index) script_code = derive_script_code(txi, pubkeyhash) # 13b. scriptCode write_varint(h_preimage, len(script_code)) write_bytes(h_preimage, script_code) write_uint64(h_preimage, txi.amount) # 13c. value write_uint32(h_preimage, txi.sequence) # 13d. nSequence return get_tx_hash(h_preimage)
class DecredPrefixHasher: """ While Decred does not have the exact same implementation as bip143/zip143, the semantics for using the prefix hash of transactions are close enough that a pseudo-bip143 class can be used to store the prefix hash during the check_fee stage of transaction signature to then reuse it at the sign_tx stage without having to request the inputs again. """ def __init__(self, tx: SignTx): self.h_prefix = HashWriter(blake256()) self.last_output_bytes = None write_uint32(self.h_prefix, tx.version | DECRED_SERIALIZE_NO_WITNESS) write_varint(self.h_prefix, tx.inputs_count) def add_prevouts(self, txi: TxInputType): write_tx_input_decred(self.h_prefix, txi) def add_sequence(self, txi: TxInputType): pass def add_output_count(self, tx: SignTx): write_varint(self.h_prefix, tx.outputs_count) def add_output(self, txo_bin: TxOutputBinType): write_tx_output(self.h_prefix, txo_bin) def set_last_output_bytes(self, w_txo_bin: bytearray): """ This is required because the last serialized output obtained in `check_fee` will only be sent to the client in `sign_tx` """ self.last_output_bytes = w_txo_bin def get_last_output_bytes(self): return self.last_output_bytes def add_locktime_expiry(self, tx: SignTx): write_uint32(self.h_prefix, tx.lock_time) write_uint32(self.h_prefix, tx.expiry) def prefix_hash(self) -> bytes: return self.h_prefix.get_digest()
def preimage_hash( self, coin: CoinInfo, tx: SignTx, txi: TxInputType, pubkeyhash: bytes, sighash: int, ) -> bytes: h_preimage = HashWriter(blake2b, outlen=32, personal=b"ZcashSigHash\x19\x1b\xa8\x5b" ) # BRANCH_ID = 0x5ba81b19 / Overwinter ensure(tx.overwintered) ensure(tx.version == 3) write_uint32(h_preimage, tx.version | OVERWINTERED) # 1. nVersion | fOverwintered write_uint32(h_preimage, tx.version_group_id) # 2. nVersionGroupId write_bytes(h_preimage, bytearray(self.get_prevouts_hash())) # 3. hashPrevouts write_bytes(h_preimage, bytearray(self.get_sequence_hash())) # 4. hashSequence write_bytes(h_preimage, bytearray(self.get_outputs_hash())) # 5. hashOutputs write_bytes(h_preimage, b"\x00" * 32) # 6. hashJoinSplits write_uint32(h_preimage, tx.lock_time) # 7. nLockTime write_uint32(h_preimage, tx.expiry) # 8. expiryHeight write_uint32(h_preimage, sighash) # 9. nHashType write_bytes_reversed(h_preimage, txi.prev_hash) # 10a. outpoint write_uint32(h_preimage, txi.prev_index) script_code = derive_script_code(txi, pubkeyhash) # 10b. scriptCode write_varint(h_preimage, len(script_code)) write_bytes(h_preimage, script_code) write_uint64(h_preimage, txi.amount) # 10c. value write_uint32(h_preimage, txi.sequence) # 10d. nSequence return get_tx_hash(h_preimage)
def preimage_hash(self, coin: CoinInfo, tx: SignTx, txi: TxInputType, pubkeyhash: bytes, sighash: int) -> bytes: h_preimage = HashWriter(sha256) assert not tx.overwintered write_uint32(h_preimage, tx.version) # nVersion write_bytes(h_preimage, bytearray(self.get_prevouts_hash())) # hashPrevouts write_bytes(h_preimage, bytearray(self.get_sequence_hash())) # hashSequence write_bytes_rev(h_preimage, txi.prev_hash) # outpoint write_uint32(h_preimage, txi.prev_index) # outpoint script_code = self.derive_script_code(txi, pubkeyhash) # scriptCode write_varint(h_preimage, len(script_code)) write_bytes(h_preimage, script_code) write_uint64(h_preimage, txi.amount) # amount write_uint32(h_preimage, txi.sequence) # nSequence write_bytes(h_preimage, bytearray(self.get_outputs_hash())) # hashOutputs write_uint32(h_preimage, tx.lock_time) # nLockTime write_uint32(h_preimage, sighash) # nHashType return get_tx_hash(h_preimage, True)
async def process_unknown_action(ctx, w, action): checksum = HashWriter(sha256()) writers.write_variant32(checksum, action.unknown.data_size) checksum.extend(action.unknown.data_chunk) writers.write_bytes(w, action.unknown.data_chunk) bytes_left = action.unknown.data_size - len(action.unknown.data_chunk) while bytes_left != 0: action = await ctx.call(EosTxActionRequest(data_size=bytes_left), EosTxActionAck) if action.unknown is None: raise ValueError("Bad response. Unknown struct expected.") checksum.extend(action.unknown.data_chunk) writers.write_bytes(w, action.unknown.data_chunk) bytes_left -= len(action.unknown.data_chunk) if bytes_left < 0: raise ValueError("Bad response. Buffer overflow.") await layout.confirm_action_unknown(ctx, action.common, checksum.get_digest())
async def get_legacy_tx_digest( self, index: int, tx_info: Union[TxInfo, OriginalTxInfo], script_pubkey: Optional[bytes] = None, ) -> Tuple[bytes, TxInput, Optional[bip32.HDNode]]: tx_hash = tx_info.orig_hash if isinstance(tx_info, OriginalTxInfo) else None # the transaction digest which gets signed for this input h_sign = self.create_hash_writer() # should come out the same as h_tx_check, checked before signing the digest h_check = HashWriter(sha256()) self.write_tx_header(h_sign, tx_info.tx, witness_marker=False) write_bitcoin_varint(h_sign, tx_info.tx.inputs_count) for i in range(tx_info.tx.inputs_count): # STAGE_REQUEST_4_INPUT in legacy txi = await helpers.request_tx_input(self.tx_req, i, self.coin, tx_hash) writers.write_tx_input_check(h_check, txi) # Only the previous UTXO's scriptPubKey is included in h_sign. if i == index: txi_sign = txi node = None if not script_pubkey: self.tx_info.check_input(txi) node = self.keychain.derive(txi.address_n) key_sign_pub = node.public_key() if txi.multisig: # Sanity check to ensure we are signing with a key that is included in the multisig. multisig.multisig_pubkey_index(txi.multisig, key_sign_pub) if txi.script_type == InputScriptType.SPENDMULTISIG: assert txi.multisig is not None # checked in sanitize_tx_input script_pubkey = scripts.output_script_multisig( multisig.multisig_get_pubkeys(txi.multisig), txi.multisig.m, ) elif txi.script_type == InputScriptType.SPENDADDRESS: script_pubkey = scripts.output_script_p2pkh( addresses.ecdsa_hash_pubkey( key_sign_pub, self.coin)) else: raise wire.ProcessError("Unknown transaction type") self.write_tx_input(h_sign, txi, script_pubkey) else: self.write_tx_input(h_sign, txi, bytes()) write_bitcoin_varint(h_sign, tx_info.tx.outputs_count) for i in range(tx_info.tx.outputs_count): # STAGE_REQUEST_4_OUTPUT in legacy txo = await helpers.request_tx_output(self.tx_req, i, self.coin, tx_hash) script_pubkey = self.output_derive_script(txo) self.write_tx_output(h_check, txo, script_pubkey) self.write_tx_output(h_sign, txo, script_pubkey) writers.write_uint32(h_sign, tx_info.tx.lock_time) writers.write_uint32(h_sign, self.get_sighash_type(txi_sign)) # check that the inputs were the same as those streamed for approval if tx_info.get_tx_check_digest() != h_check.get_digest(): raise wire.ProcessError("Transaction has changed during signing") tx_digest = writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double) return tx_digest, txi_sign, node
def init_hash143(self) -> None: self.h_prevouts = HashWriter(sha256()) self.h_sequence = HashWriter(sha256()) self.h_outputs = HashWriter(sha256())
def __init__(self): self.h_prevouts = HashWriter(blake2b, b'', 32, b'ZcashPrevoutHash') self.h_sequence = HashWriter(blake2b, b'', 32, b'ZcashSequencHash') self.h_outputs = HashWriter(blake2b, b'', 32, b'ZcashOutputsHash')
def address_multisig_p2wsh(pubkeys: list[bytes], m: int, hrp: str) -> str: if not hrp: raise wire.ProcessError("Multisig not enabled on this coin") witness_script_h = HashWriter(sha256()) write_output_script_multisig(witness_script_h, pubkeys, m) return address_p2wsh(witness_script_h.get_digest(), hrp)
def create_hash_writer(self) -> HashWriter: return HashWriter(blake256())
async def get_prevtx_output_value(coin: CoinInfo, tx_req: TxRequest, prev_hash: bytes, prev_index: int) -> int: total_out = 0 # sum of output amounts # STAGE_REQUEST_2_PREV_META tx = await request_tx_meta(tx_req, prev_hash) if coin.decred: txh = HashWriter(blake256()) else: txh = HashWriter(sha256()) if tx.overwintered: write_uint32(txh, tx.version | OVERWINTERED) # nVersion | fOverwintered write_uint32(txh, tx.version_group_id) # nVersionGroupId elif coin.decred: write_uint32(txh, tx.version | DECRED_SERIALIZE_NO_WITNESS) else: write_uint32(txh, tx.version) # nVersion write_varint(txh, tx.inputs_cnt) for i in range(tx.inputs_cnt): # STAGE_REQUEST_2_PREV_INPUT txi = await request_tx_input(tx_req, i, prev_hash) if coin.decred: write_tx_input_decred(txh, txi) else: write_tx_input(txh, txi) write_varint(txh, tx.outputs_cnt) for o in range(tx.outputs_cnt): # STAGE_REQUEST_2_PREV_OUTPUT txo_bin = await request_tx_output(tx_req, o, prev_hash) write_tx_output(txh, txo_bin) if o == prev_index: total_out += txo_bin.amount if (coin.decred and txo_bin.decred_script_version is not None and txo_bin.decred_script_version != 0): raise SigningError( FailureType.ProcessError, "Cannot use utxo that has script_version != 0", ) write_uint32(txh, tx.lock_time) if tx.overwintered or coin.decred: write_uint32(txh, tx.expiry) ofs = 0 while ofs < tx.extra_data_len: size = min(1024, tx.extra_data_len - ofs) data = await request_tx_extra_data(tx_req, ofs, size, prev_hash) write_bytes(txh, data) ofs += len(data) if get_tx_hash(txh, double=coin.sign_hash_double, reverse=True) != prev_hash: raise SigningError(FailureType.ProcessError, "Encountered invalid prev_hash") return total_out
def tagged_hashwriter(tag: bytes) -> HashWriter: tag_digest = sha256(tag).digest() ctx = sha256(tag_digest) ctx.update(tag_digest) return HashWriter(ctx)
def __init__(self): self.h_prevouts = HashWriter(sha256) self.h_sequence = HashWriter(sha256) self.h_outputs = HashWriter(sha256)
def __init__(self) -> None: self.h_prevouts = HashWriter(sha256()) self.h_amounts = HashWriter(sha256()) self.h_scriptpubkeys = HashWriter(sha256()) self.h_sequences = HashWriter(sha256()) self.h_outputs = HashWriter(sha256())
def init_hash143(self) -> None: self.h_prevouts = HashWriter(blake2b(outlen=32, personal=b"ZcashPrevoutHash")) self.h_sequence = HashWriter(blake2b(outlen=32, personal=b"ZcashSequencHash")) self.h_outputs = HashWriter(blake2b(outlen=32, personal=b"ZcashOutputsHash"))
def hash143_preimage_hash(self, txi: TxInputType, pubkeyhash: bytes) -> bytes: h_preimage = HashWriter( blake2b( outlen=32, personal=b"ZcashSigHash" + struct.pack("<I", self.tx.branch_id), ) ) # 1. nVersion | fOverwintered write_uint32(h_preimage, self.tx.version | OVERWINTERED) # 2. nVersionGroupId write_uint32(h_preimage, self.tx.version_group_id) # 3. hashPrevouts write_bytes_fixed(h_preimage, get_tx_hash(self.h_prevouts), TX_HASH_SIZE) # 4. hashSequence write_bytes_fixed(h_preimage, get_tx_hash(self.h_sequence), TX_HASH_SIZE) # 5. hashOutputs write_bytes_fixed(h_preimage, get_tx_hash(self.h_outputs), TX_HASH_SIZE) if self.tx.version == 3: # 6. hashJoinSplits write_bytes_fixed(h_preimage, b"\x00" * TX_HASH_SIZE, TX_HASH_SIZE) # 7. nLockTime write_uint32(h_preimage, self.tx.lock_time) # 8. expiryHeight write_uint32(h_preimage, self.tx.expiry) # 9. nHashType write_uint32(h_preimage, self.get_hash_type()) elif self.tx.version == 4: zero_hash = b"\x00" * TX_HASH_SIZE # 6. hashJoinSplits write_bytes_fixed(h_preimage, zero_hash, TX_HASH_SIZE) # 7. hashShieldedSpends write_bytes_fixed(h_preimage, zero_hash, TX_HASH_SIZE) # 8. hashShieldedOutputs write_bytes_fixed(h_preimage, zero_hash, TX_HASH_SIZE) # 9. nLockTime write_uint32(h_preimage, self.tx.lock_time) # 10. expiryHeight write_uint32(h_preimage, self.tx.expiry) # 11. valueBalance write_uint64(h_preimage, 0) # 12. nHashType write_uint32(h_preimage, self.get_hash_type()) else: raise wire.DataError("Unsupported version for overwintered transaction") # 10a /13a. outpoint write_bytes_reversed(h_preimage, txi.prev_hash, TX_HASH_SIZE) write_uint32(h_preimage, txi.prev_index) # 10b / 13b. scriptCode script_code = derive_script_code(txi, pubkeyhash) write_bytes_prefixed(h_preimage, script_code) # 10c / 13c. value write_uint64(h_preimage, txi.amount) # 10d / 13d. nSequence write_uint32(h_preimage, txi.sequence) return get_tx_hash(h_preimage)
async def sign_tx(tx: SignTx, root: bip32.HDNode): tx = sanitize_sign_tx(tx) progress.init(tx.inputs_count, tx.outputs_count) # Phase 1 h_first, bip143, segwit, authorized_in, wallet_path = await check_tx_fee( tx, root) # Phase 2 # - sign inputs # - check that nothing changed coin = coins.by_name(tx.coin_name) tx_ser = TxRequestSerializedType() txo_bin = TxOutputBinType() tx_req = TxRequest() tx_req.details = TxRequestDetailsType() tx_req.serialized = None for i_sign in range(tx.inputs_count): progress.advance() txi_sign = None key_sign = None key_sign_pub = None if segwit[i_sign]: # STAGE_REQUEST_SEGWIT_INPUT txi_sign = await request_tx_input(tx_req, i_sign) is_segwit = (txi_sign.script_type == InputScriptType.SPENDWITNESS or txi_sign.script_type == InputScriptType.SPENDP2SHWITNESS) if not is_segwit: raise SigningError(FailureType.ProcessError, 'Transaction has changed during signing') input_check_wallet_path(txi_sign, wallet_path) key_sign = node_derive(root, txi_sign.address_n) key_sign_pub = key_sign.public_key() txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub) w_txi = bytearray_with_cap(7 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4) if i_sign == 0: # serializing first input => prepend headers write_bytes(w_txi, get_tx_header(tx, True)) write_tx_input(w_txi, txi_sign) tx_ser.serialized_tx = w_txi tx_req.serialized = tx_ser elif coin.force_bip143: # STAGE_REQUEST_SEGWIT_INPUT txi_sign = await request_tx_input(tx_req, i_sign) input_check_wallet_path(txi_sign, wallet_path) is_bip143 = (txi_sign.script_type == InputScriptType.SPENDADDRESS or txi_sign.script_type == InputScriptType.SPENDMULTISIG) if not is_bip143 or txi_sign.amount > authorized_in: raise SigningError(FailureType.ProcessError, 'Transaction has changed during signing') authorized_in -= txi_sign.amount key_sign = node_derive(root, txi_sign.address_n) key_sign_pub = key_sign.public_key() bip143_hash = bip143.preimage_hash(tx, txi_sign, ecdsa_hash_pubkey(key_sign_pub), get_hash_type(coin)) # if multisig, check if singing with a key that is included in multisig if txi_sign.multisig: multisig_pubkey_index(txi_sign.multisig, key_sign_pub) signature = ecdsa_sign(key_sign, bip143_hash) tx_ser.signature_index = i_sign tx_ser.signature = signature # serialize input with correct signature txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub, signature) w_txi_sign = bytearray_with_cap(5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4) if i_sign == 0: # serializing first input => prepend headers write_bytes(w_txi_sign, get_tx_header(tx)) write_tx_input(w_txi_sign, txi_sign) tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser else: # hash of what we are signing with this input h_sign = HashWriter(sha256) # same as h_first, checked before signing the digest h_second = HashWriter(sha256) write_uint32(h_sign, tx.version) write_varint(h_sign, tx.inputs_count) for i in range(tx.inputs_count): # STAGE_REQUEST_4_INPUT txi = await request_tx_input(tx_req, i) input_check_wallet_path(txi, wallet_path) write_tx_input_check(h_second, txi) if i == i_sign: txi_sign = txi key_sign = node_derive(root, txi.address_n) key_sign_pub = key_sign.public_key() # for the signing process the script_sig is equal # to the previous tx's scriptPubKey (P2PKH) or a redeem script (P2SH) if txi_sign.script_type == InputScriptType.SPENDMULTISIG: txi_sign.script_sig = output_script_multisig( multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m) elif txi_sign.script_type == InputScriptType.SPENDADDRESS: txi_sign.script_sig = output_script_p2pkh( ecdsa_hash_pubkey(key_sign_pub)) else: raise SigningError(FailureType.ProcessError, 'Unknown transaction type') else: txi.script_sig = bytes() write_tx_input(h_sign, txi) write_varint(h_sign, tx.outputs_count) for o in range(tx.outputs_count): # STAGE_REQUEST_4_OUTPUT txo = await request_tx_output(tx_req, o) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, root) write_tx_output(h_second, txo_bin) write_tx_output(h_sign, txo_bin) write_uint32(h_sign, tx.lock_time) write_uint32(h_sign, get_hash_type(coin)) # check the control digests if get_tx_hash(h_first, False) != get_tx_hash(h_second, False): raise SigningError(FailureType.ProcessError, 'Transaction has changed during signing') # if multisig, check if singing with a key that is included in multisig if txi_sign.multisig: multisig_pubkey_index(txi_sign.multisig, key_sign_pub) # compute the signature from the tx digest signature = ecdsa_sign(key_sign, get_tx_hash(h_sign, True)) tx_ser.signature_index = i_sign tx_ser.signature = signature # serialize input with correct signature txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub, signature) w_txi_sign = bytearray_with_cap(5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4) if i_sign == 0: # serializing first input => prepend headers write_bytes(w_txi_sign, get_tx_header(tx)) write_tx_input(w_txi_sign, txi_sign) tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser for o in range(tx.outputs_count): progress.advance() # STAGE_REQUEST_5_OUTPUT txo = await request_tx_output(tx_req, o) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, root) # serialize output w_txo_bin = bytearray_with_cap(5 + 8 + 5 + len(txo_bin.script_pubkey) + 4) if o == 0: # serializing first output => prepend outputs count write_varint(w_txo_bin, tx.outputs_count) write_tx_output(w_txo_bin, txo_bin) tx_ser.signature_index = None tx_ser.signature = None tx_ser.serialized_tx = w_txo_bin tx_req.serialized = tx_ser any_segwit = True in segwit.values() for i in range(tx.inputs_count): progress.advance() if segwit[i]: # STAGE_REQUEST_SEGWIT_WITNESS txi = await request_tx_input(tx_req, i) input_check_wallet_path(txi, wallet_path) is_segwit = (txi.script_type == InputScriptType.SPENDWITNESS or txi.script_type == InputScriptType.SPENDP2SHWITNESS) if not is_segwit or txi.amount > authorized_in: raise SigningError(FailureType.ProcessError, 'Transaction has changed during signing') authorized_in -= txi.amount key_sign = node_derive(root, txi.address_n) key_sign_pub = key_sign.public_key() bip143_hash = bip143.preimage_hash(tx, txi, ecdsa_hash_pubkey(key_sign_pub), get_hash_type(coin)) signature = ecdsa_sign(key_sign, bip143_hash) if txi.multisig: # find out place of our signature based on the pubkey signature_index = multisig_pubkey_index( txi.multisig, key_sign_pub) witness = witness_p2wsh(txi.multisig, signature, signature_index, get_hash_type(coin)) else: witness = witness_p2wpkh(signature, key_sign_pub, get_hash_type(coin)) tx_ser.serialized_tx = witness tx_ser.signature_index = i tx_ser.signature = signature elif any_segwit: tx_ser.serialized_tx = bytearray( 1) # empty witness for non-segwit inputs tx_ser.signature_index = None tx_ser.signature = None tx_req.serialized = tx_ser write_uint32(tx_ser.serialized_tx, tx.lock_time) await request_tx_finish(tx_req)
async def sign_tx(tx: SignTx, root: bip32.HDNode): tx = sanitize_sign_tx(tx) progress.init(tx.inputs_count, tx.outputs_count) # Phase 1 h_first, hash143, segwit, authorized_in, wallet_path = await check_tx_fee( tx, root) # Phase 2 # - sign inputs # - check that nothing changed coin = coins.by_name(tx.coin_name) tx_ser = TxRequestSerializedType() txo_bin = TxOutputBinType() tx_req = TxRequest() tx_req.details = TxRequestDetailsType() tx_req.serialized = None if coin.decred: prefix_hash = hash143.prefix_hash() for i_sign in range(tx.inputs_count): progress.advance() txi_sign = None key_sign = None key_sign_pub = None if segwit[i_sign]: # STAGE_REQUEST_SEGWIT_INPUT txi_sign = await request_tx_input(tx_req, i_sign) is_segwit = (txi_sign.script_type == InputScriptType.SPENDWITNESS or txi_sign.script_type == InputScriptType.SPENDP2SHWITNESS) if not is_segwit: raise SigningError(FailureType.ProcessError, "Transaction has changed during signing") input_check_wallet_path(txi_sign, wallet_path) key_sign = node_derive(root, txi_sign.address_n) key_sign_pub = key_sign.public_key() txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub) w_txi = empty_bytearray(7 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4) if i_sign == 0: # serializing first input => prepend headers write_bytes(w_txi, get_tx_header(coin, tx, True)) write_tx_input(w_txi, txi_sign) tx_ser.serialized_tx = w_txi tx_req.serialized = tx_ser elif coin.force_bip143 or tx.overwintered: # STAGE_REQUEST_SEGWIT_INPUT txi_sign = await request_tx_input(tx_req, i_sign) input_check_wallet_path(txi_sign, wallet_path) is_bip143 = (txi_sign.script_type == InputScriptType.SPENDADDRESS or txi_sign.script_type == InputScriptType.SPENDMULTISIG) if not is_bip143 or txi_sign.amount > authorized_in: raise SigningError(FailureType.ProcessError, "Transaction has changed during signing") authorized_in -= txi_sign.amount key_sign = node_derive(root, txi_sign.address_n) key_sign_pub = key_sign.public_key() hash143_hash = hash143.preimage_hash( coin, tx, txi_sign, ecdsa_hash_pubkey(key_sign_pub, coin), get_hash_type(coin), ) # if multisig, check if singing with a key that is included in multisig if txi_sign.multisig: multisig_pubkey_index(txi_sign.multisig, key_sign_pub) signature = ecdsa_sign(key_sign, hash143_hash) tx_ser.signature_index = i_sign tx_ser.signature = signature # serialize input with correct signature txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub, signature) w_txi_sign = empty_bytearray(5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4) if i_sign == 0: # serializing first input => prepend headers write_bytes(w_txi_sign, get_tx_header(coin, tx)) write_tx_input(w_txi_sign, txi_sign) tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser elif coin.decred: txi_sign = await request_tx_input(tx_req, i_sign) input_check_wallet_path(txi_sign, wallet_path) key_sign = node_derive(root, txi_sign.address_n) key_sign_pub = key_sign.public_key() if txi_sign.script_type == InputScriptType.SPENDMULTISIG: prev_pkscript = output_script_multisig( multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m) elif txi_sign.script_type == InputScriptType.SPENDADDRESS: prev_pkscript = output_script_p2pkh( ecdsa_hash_pubkey(key_sign_pub, coin)) else: raise ValueError("Unknown input script type") h_witness = HashWriter(blake256()) write_uint32(h_witness, tx.version | DECRED_SERIALIZE_WITNESS_SIGNING) write_varint(h_witness, tx.inputs_count) for ii in range(tx.inputs_count): if ii == i_sign: write_varint(h_witness, len(prev_pkscript)) write_bytes(h_witness, prev_pkscript) else: write_varint(h_witness, 0) witness_hash = get_tx_hash(h_witness, double=coin.sign_hash_double, reverse=False) h_sign = HashWriter(blake256()) write_uint32(h_sign, DECRED_SIGHASHALL) write_bytes(h_sign, prefix_hash) write_bytes(h_sign, witness_hash) sig_hash = get_tx_hash(h_sign, double=coin.sign_hash_double) signature = ecdsa_sign(key_sign, sig_hash) tx_ser.signature_index = i_sign tx_ser.signature = signature # serialize input with correct signature txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub, signature) w_txi_sign = empty_bytearray( 8 + 4 + len(hash143.get_last_output_bytes()) if i_sign == 0 else 0 + 16 + 4 + len(txi_sign.script_sig)) if i_sign == 0: write_bytes(w_txi_sign, hash143.get_last_output_bytes()) write_uint32(w_txi_sign, tx.lock_time) write_uint32(w_txi_sign, tx.expiry) write_varint(w_txi_sign, tx.inputs_count) write_tx_input_decred_witness(w_txi_sign, txi_sign) tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser else: # hash of what we are signing with this input h_sign = HashWriter(sha256()) # same as h_first, checked before signing the digest h_second = HashWriter(sha256()) if tx.overwintered: write_uint32(h_sign, tx.version | OVERWINTERED) # nVersion | fOverwintered write_uint32(h_sign, tx.version_group_id) # nVersionGroupId else: write_uint32(h_sign, tx.version) # nVersion write_varint(h_sign, tx.inputs_count) for i in range(tx.inputs_count): # STAGE_REQUEST_4_INPUT txi = await request_tx_input(tx_req, i) input_check_wallet_path(txi, wallet_path) write_tx_input_check(h_second, txi) if i == i_sign: txi_sign = txi key_sign = node_derive(root, txi.address_n) key_sign_pub = key_sign.public_key() # for the signing process the script_sig is equal # to the previous tx's scriptPubKey (P2PKH) or a redeem script (P2SH) if txi_sign.script_type == InputScriptType.SPENDMULTISIG: txi_sign.script_sig = output_script_multisig( multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m) elif txi_sign.script_type == InputScriptType.SPENDADDRESS: txi_sign.script_sig = output_script_p2pkh( ecdsa_hash_pubkey(key_sign_pub, coin)) if coin.bip115: txi_sign.script_sig += script_replay_protection_bip115( txi_sign.prev_block_hash_bip115, txi_sign.prev_block_height_bip115, ) else: raise SigningError(FailureType.ProcessError, "Unknown transaction type") else: txi.script_sig = bytes() write_tx_input(h_sign, txi) write_varint(h_sign, tx.outputs_count) for o in range(tx.outputs_count): # STAGE_REQUEST_4_OUTPUT txo = await request_tx_output(tx_req, o) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, root) write_tx_output(h_second, txo_bin) write_tx_output(h_sign, txo_bin) write_uint32(h_sign, tx.lock_time) if tx.overwintered: write_uint32(h_sign, tx.expiry) # expiryHeight write_varint(h_sign, 0) # nJoinSplit write_uint32(h_sign, get_hash_type(coin)) # check the control digests if get_tx_hash(h_first, False) != get_tx_hash(h_second): raise SigningError(FailureType.ProcessError, "Transaction has changed during signing") # if multisig, check if singing with a key that is included in multisig if txi_sign.multisig: multisig_pubkey_index(txi_sign.multisig, key_sign_pub) # compute the signature from the tx digest signature = ecdsa_sign( key_sign, get_tx_hash(h_sign, double=coin.sign_hash_double)) tx_ser.signature_index = i_sign tx_ser.signature = signature # serialize input with correct signature txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub, signature) w_txi_sign = empty_bytearray(5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4) if i_sign == 0: # serializing first input => prepend headers write_bytes(w_txi_sign, get_tx_header(coin, tx)) write_tx_input(w_txi_sign, txi_sign) tx_ser.serialized_tx = w_txi_sign tx_req.serialized = tx_ser if coin.decred: return await request_tx_finish(tx_req) for o in range(tx.outputs_count): progress.advance() # STAGE_REQUEST_5_OUTPUT txo = await request_tx_output(tx_req, o) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, root) # serialize output w_txo_bin = empty_bytearray(5 + 8 + 5 + len(txo_bin.script_pubkey) + 4) if o == 0: # serializing first output => prepend outputs count write_varint(w_txo_bin, tx.outputs_count) write_tx_output(w_txo_bin, txo_bin) tx_ser.signature_index = None tx_ser.signature = None tx_ser.serialized_tx = w_txo_bin tx_req.serialized = tx_ser any_segwit = True in segwit.values() for i in range(tx.inputs_count): progress.advance() if segwit[i]: # STAGE_REQUEST_SEGWIT_WITNESS txi = await request_tx_input(tx_req, i) input_check_wallet_path(txi, wallet_path) is_segwit = (txi.script_type == InputScriptType.SPENDWITNESS or txi.script_type == InputScriptType.SPENDP2SHWITNESS) if not is_segwit or txi.amount > authorized_in: raise SigningError(FailureType.ProcessError, "Transaction has changed during signing") authorized_in -= txi.amount key_sign = node_derive(root, txi.address_n) key_sign_pub = key_sign.public_key() hash143_hash = hash143.preimage_hash( coin, tx, txi, ecdsa_hash_pubkey(key_sign_pub, coin), get_hash_type(coin), ) signature = ecdsa_sign(key_sign, hash143_hash) if txi.multisig: # find out place of our signature based on the pubkey signature_index = multisig_pubkey_index( txi.multisig, key_sign_pub) witness = witness_p2wsh(txi.multisig, signature, signature_index, get_hash_type(coin)) else: witness = witness_p2wpkh(signature, key_sign_pub, get_hash_type(coin)) tx_ser.serialized_tx = witness tx_ser.signature_index = i tx_ser.signature = signature elif any_segwit: tx_ser.serialized_tx = bytearray( 1) # empty witness for non-segwit inputs tx_ser.signature_index = None tx_ser.signature = None tx_req.serialized = tx_ser write_uint32(tx_ser.serialized_tx, tx.lock_time) if tx.overwintered: if tx.version == 3: write_uint32(tx_ser.serialized_tx, tx.expiry) # expiryHeight write_varint(tx_ser.serialized_tx, 0) # nJoinSplit elif tx.version == 4: write_uint32(tx_ser.serialized_tx, tx.expiry) # expiryHeight write_uint64(tx_ser.serialized_tx, 0) # valueBalance write_varint(tx_ser.serialized_tx, 0) # nShieldedSpend write_varint(tx_ser.serialized_tx, 0) # nShieldedOutput write_varint(tx_ser.serialized_tx, 0) # nJoinSplit else: raise SigningError( FailureType.DataError, "Unsupported version for overwintered transaction", ) await request_tx_finish(tx_req)
async def ethereum_sign_tx(ctx, msg): from trezor.crypto.hashlib import sha3_256 msg = sanitize(msg) check(msg) data_total = msg.data_length # detect ERC - 20 token token = None recipient = msg.to value = int.from_bytes(msg.value, "big") if (len(msg.to) == 20 and len(msg.value) == 0 and data_total == 68 and len(msg.data_initial_chunk) == 68 and msg.data_initial_chunk[:16] == b"\xa9\x05\x9c\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ): token = tokens.token_by_chain_address(msg.chain_id, msg.to) recipient = msg.data_initial_chunk[16:36] value = int.from_bytes(msg.data_initial_chunk[36:68], "big") await require_confirm_tx(ctx, recipient, value, msg.chain_id, token, msg.tx_type) if token is None and msg.data_length > 0: await require_confirm_data(ctx, msg.data_initial_chunk, data_total) await require_confirm_fee( ctx, value, int.from_bytes(msg.gas_price, "big"), int.from_bytes(msg.gas_limit, "big"), msg.chain_id, token, msg.tx_type, ) data = bytearray() data += msg.data_initial_chunk data_left = data_total - len(msg.data_initial_chunk) total_length = get_total_length(msg, data_total) sha = HashWriter(sha3_256) sha.extend(rlp.encode_length(total_length, True)) # total length if msg.tx_type is not None: sha.extend(rlp.encode(msg.tx_type)) for field in [msg.nonce, msg.gas_price, msg.gas_limit, msg.to, msg.value]: sha.extend(rlp.encode(field)) if data_left == 0: sha.extend(rlp.encode(data)) else: sha.extend(rlp.encode_length(data_total, False)) sha.extend(rlp.encode(data, False)) while data_left > 0: resp = await send_request_chunk(ctx, data_left) data_left -= len(resp.data_chunk) sha.extend(resp.data_chunk) # eip 155 replay protection if msg.chain_id: sha.extend(rlp.encode(msg.chain_id)) sha.extend(rlp.encode(0)) sha.extend(rlp.encode(0)) digest = sha.get_digest(True) # True -> use keccak mode return await send_signature(ctx, msg, digest)
async def check_tx_fee(tx: SignTx, root: bip32.HDNode): coin = coins.by_name(tx.coin_name) # h_first is used to make sure the inputs and outputs streamed in Phase 1 # are the same as in Phase 2. it is thus not required to fully hash the # tx, as the SignTx info is streamed only once h_first = HashWriter(sha256()) # not a real tx hash if coin.decred: hash143 = DecredPrefixHasher(tx) # pseudo bip143 prefix hashing tx_ser = TxRequestSerializedType() elif tx.overwintered: if tx.version == 3: hash143 = Zip143() # ZIP-0143 transaction hashing elif tx.version == 4: hash143 = Zip243() # ZIP-0243 transaction hashing else: raise SigningError( FailureType.DataError, "Unsupported version for overwintered transaction", ) else: hash143 = Bip143() # BIP-0143 transaction hashing multifp = MultisigFingerprint() # control checksum of multisig inputs weight = TxWeightCalculator(tx.inputs_count, tx.outputs_count) total_in = 0 # sum of input amounts segwit_in = 0 # sum of segwit input amounts total_out = 0 # sum of output amounts change_out = 0 # change output amount wallet_path = [] # common prefix of input paths segwit = {} # dict of booleans stating if input is segwit # output structures txo_bin = TxOutputBinType() tx_req = TxRequest() tx_req.details = TxRequestDetailsType() for i in range(tx.inputs_count): progress.advance() # STAGE_REQUEST_1_INPUT txi = await request_tx_input(tx_req, i) wallet_path = input_extract_wallet_path(txi, wallet_path) write_tx_input_check(h_first, txi) weight.add_input(txi) hash143.add_prevouts( txi) # all inputs are included (non-segwit as well) hash143.add_sequence(txi) if not address_n_matches_coin(txi.address_n, coin): await confirm_foreign_address(txi.address_n, coin) if txi.multisig: multifp.add(txi.multisig) if txi.script_type in ( InputScriptType.SPENDWITNESS, InputScriptType.SPENDP2SHWITNESS, ): if not coin.segwit: raise SigningError(FailureType.DataError, "Segwit not enabled on this coin") if not txi.amount: raise SigningError(FailureType.DataError, "Segwit input without amount") segwit[i] = True segwit_in += txi.amount total_in += txi.amount elif txi.script_type in ( InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG, ): if coin.force_bip143 or tx.overwintered: if not txi.amount: raise SigningError(FailureType.DataError, "Expected input with amount") segwit[i] = False segwit_in += txi.amount total_in += txi.amount else: segwit[i] = False total_in += await get_prevtx_output_value( coin, tx_req, txi.prev_hash, txi.prev_index) else: raise SigningError(FailureType.DataError, "Wrong input script type") if coin.decred: w_txi = empty_bytearray(8 if i == 0 else 0 + 9 + len(txi.prev_hash)) if i == 0: # serializing first input => prepend headers write_bytes(w_txi, get_tx_header(coin, tx)) write_tx_input_decred(w_txi, txi) tx_ser.serialized_tx = w_txi tx_req.serialized = tx_ser if coin.decred: hash143.add_output_count(tx) for o in range(tx.outputs_count): # STAGE_REQUEST_3_OUTPUT txo = await request_tx_output(tx_req, o) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, root) weight.add_output(txo_bin.script_pubkey) if change_out == 0 and is_change(txo, wallet_path, segwit_in, multifp): # output is change and does not need confirmation change_out = txo.amount elif not await confirm_output(txo, coin): raise SigningError(FailureType.ActionCancelled, "Output cancelled") if coin.decred: if txo.decred_script_version is not None and txo.decred_script_version != 0: raise SigningError( FailureType.ActionCancelled, "Cannot send to output with script version != 0", ) txo_bin.decred_script_version = txo.decred_script_version w_txo_bin = empty_bytearray(4 + 8 + 2 + 4 + len(txo_bin.script_pubkey)) if o == 0: # serializing first output => prepend outputs count write_varint(w_txo_bin, tx.outputs_count) write_tx_output(w_txo_bin, txo_bin) tx_ser.serialized_tx = w_txo_bin tx_req.serialized = tx_ser hash143.set_last_output_bytes(w_txo_bin) write_tx_output(h_first, txo_bin) hash143.add_output(txo_bin) total_out += txo_bin.amount fee = total_in - total_out if fee < 0: raise SigningError(FailureType.NotEnoughFunds, "Not enough funds") # fee > (coin.maxfee per byte * tx size) if fee > (coin.maxfee_kb / 1000) * (weight.get_total() / 4): if not await confirm_feeoverthreshold(fee, coin): raise SigningError(FailureType.ActionCancelled, "Signing cancelled") if not await confirm_total(total_in - change_out, fee, coin): raise SigningError(FailureType.ActionCancelled, "Total cancelled") if coin.decred: hash143.add_locktime_expiry(tx) return h_first, hash143, segwit, total_in, wallet_path
def __init__(self) -> None: self.h_prevouts = HashWriter(sha256()) self.h_sequence = HashWriter(sha256()) self.h_outputs = HashWriter(sha256())
async def check_tx_fee(tx: SignTx, root: bip32.HDNode): coin = coins.by_name(tx.coin_name) # h_first is used to make sure the inputs and outputs streamed in Phase 1 # are the same as in Phase 2. it is thus not required to fully hash the # tx, as the SignTx info is streamed only once h_first = HashWriter(sha256) # not a real tx hash bip143 = Bip143() # bip143 transaction hashing multifp = MultisigFingerprint() # control checksum of multisig inputs weight = TxWeightCalculator(tx.inputs_count, tx.outputs_count) total_in = 0 # sum of input amounts segwit_in = 0 # sum of segwit input amounts total_out = 0 # sum of output amounts change_out = 0 # change output amount wallet_path = [] # common prefix of input paths segwit = {} # dict of booleans stating if input is segwit # output structures txo_bin = TxOutputBinType() tx_req = TxRequest() tx_req.details = TxRequestDetailsType() for i in range(tx.inputs_count): progress.advance() # STAGE_REQUEST_1_INPUT txi = await request_tx_input(tx_req, i) wallet_path = input_extract_wallet_path(txi, wallet_path) write_tx_input_check(h_first, txi) weight.add_input(txi) bip143.add_prevouts( txi) # all inputs are included (non-segwit as well) bip143.add_sequence(txi) if txi.multisig: multifp.add(txi.multisig) if txi.script_type in (InputScriptType.SPENDWITNESS, InputScriptType.SPENDP2SHWITNESS): if not coin.segwit: raise SigningError(FailureType.DataError, 'Segwit not enabled on this coin') if not txi.amount: raise SigningError(FailureType.DataError, 'Segwit input without amount') segwit[i] = True segwit_in += txi.amount total_in += txi.amount elif txi.script_type in (InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG): if coin.force_bip143: if not txi.amount: raise SigningError(FailureType.DataError, 'BIP 143 input without amount') segwit[i] = False segwit_in += txi.amount total_in += txi.amount else: segwit[i] = False total_in += await get_prevtx_output_value( tx_req, txi.prev_hash, txi.prev_index) else: raise SigningError(FailureType.DataError, 'Wrong input script type') for o in range(tx.outputs_count): # STAGE_REQUEST_3_OUTPUT txo = await request_tx_output(tx_req, o) txo_bin.amount = txo.amount txo_bin.script_pubkey = output_derive_script(txo, coin, root) weight.add_output(txo_bin.script_pubkey) if change_out == 0 and is_change(txo, wallet_path, segwit_in, multifp): # output is change and does not need confirmation change_out = txo.amount elif not await confirm_output(txo, coin): raise SigningError(FailureType.ActionCancelled, 'Output cancelled') write_tx_output(h_first, txo_bin) bip143.add_output(txo_bin) total_out += txo_bin.amount fee = total_in - total_out if fee < 0: raise SigningError(FailureType.NotEnoughFunds, 'Not enough funds') # fee > (coin.maxfee per byte * tx size) if fee > (coin.maxfee_kb / 1000) * (weight.get_total() / 4): if not await confirm_feeoverthreshold(fee, coin): raise SigningError(FailureType.ActionCancelled, 'Signing cancelled') if not await confirm_total(total_out - change_out, fee, coin): raise SigningError(FailureType.ActionCancelled, 'Total cancelled') return h_first, bip143, segwit, total_in, wallet_path
def address_multisig_p2sh(pubkeys: list[bytes], m: int, coin: CoinInfo) -> str: if coin.address_type_p2sh is None: raise wire.ProcessError("Multisig not enabled on this coin") redeem_script = HashWriter(coin.script_hash()) write_output_script_multisig(redeem_script, pubkeys, m) return address_p2sh(redeem_script.get_digest(), coin)
class Decred(Bitcoin): def __init__( self, tx: SignTx, keychain: Keychain, coin: CoinInfo, approver: approvers.Approver | None, ) -> None: ensure(coin.decred) self.h_prefix = HashWriter(blake256()) ensure(approver is None) approver = DecredApprover(tx, coin) super().__init__(tx, keychain, coin, approver) self.write_tx_header(self.serialized_tx, self.tx_info.tx, witness_marker=True) write_bitcoin_varint(self.serialized_tx, self.tx_info.tx.inputs_count) writers.write_uint32( self.h_prefix, self.tx_info.tx.version | DECRED_SERIALIZE_NO_WITNESS) write_bitcoin_varint(self.h_prefix, self.tx_info.tx.inputs_count) def create_hash_writer(self) -> HashWriter: return HashWriter(blake256()) def create_sig_hasher(self) -> SigHasher: return DecredSigHasher(self.h_prefix) async def step2_approve_outputs(self) -> None: write_bitcoin_varint(self.serialized_tx, self.tx_info.tx.outputs_count) write_bitcoin_varint(self.h_prefix, self.tx_info.tx.outputs_count) if self.tx_info.tx.decred_staking_ticket: await self.approve_staking_ticket() else: await super().step2_approve_outputs() self.write_tx_footer(self.serialized_tx, self.tx_info.tx) self.write_tx_footer(self.h_prefix, self.tx_info.tx) async def process_internal_input(self, txi: TxInput) -> None: await super().process_internal_input(txi) # Decred serializes inputs early. self.write_tx_input(self.serialized_tx, txi, bytes()) async def process_external_input(self, txi: TxInput) -> None: raise wire.DataError("External inputs not supported") async def process_original_input(self, txi: TxInput, script_pubkey: bytes) -> None: raise wire.DataError("Replacement transactions not supported") async def approve_output( self, txo: TxOutput, script_pubkey: bytes, orig_txo: TxOutput | None, ) -> None: await super().approve_output(txo, script_pubkey, orig_txo) self.write_tx_output(self.serialized_tx, txo, script_pubkey) async def step4_serialize_inputs(self) -> None: write_bitcoin_varint(self.serialized_tx, self.tx_info.tx.inputs_count) prefix_hash = self.h_prefix.get_digest() for i_sign in range(self.tx_info.tx.inputs_count): progress.advance() txi_sign = await helpers.request_tx_input(self.tx_req, i_sign, self.coin) self.tx_info.check_input(txi_sign) key_sign = self.keychain.derive(txi_sign.address_n) key_sign_pub = key_sign.public_key() h_witness = self.create_hash_writer() writers.write_uint32( h_witness, self.tx_info.tx.version | DECRED_SERIALIZE_WITNESS_SIGNING) write_bitcoin_varint(h_witness, self.tx_info.tx.inputs_count) for ii in range(self.tx_info.tx.inputs_count): if ii == i_sign: if txi_sign.decred_staking_spend == DecredStakingSpendType.SSRTX: scripts_decred.write_output_script_ssrtx_prefixed( h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin)) elif txi_sign.decred_staking_spend == DecredStakingSpendType.SSGen: scripts_decred.write_output_script_ssgen_prefixed( h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin)) elif txi_sign.script_type == InputScriptType.SPENDMULTISIG: assert txi_sign.multisig is not None scripts_decred.write_output_script_multisig( h_witness, multisig.multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m, prefixed=True, ) elif txi_sign.script_type == InputScriptType.SPENDADDRESS: scripts_decred.write_output_script_p2pkh( h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin), prefixed=True, ) else: raise wire.DataError("Unsupported input script type") else: write_bitcoin_varint(h_witness, 0) witness_hash = writers.get_tx_hash( h_witness, double=self.coin.sign_hash_double, reverse=False) h_sign = self.create_hash_writer() writers.write_uint32(h_sign, SigHashType.SIGHASH_ALL) writers.write_bytes_fixed(h_sign, prefix_hash, writers.TX_HASH_SIZE) writers.write_bytes_fixed(h_sign, witness_hash, writers.TX_HASH_SIZE) sig_hash = writers.get_tx_hash(h_sign, double=self.coin.sign_hash_double) signature = ecdsa_sign(key_sign, sig_hash) # serialize input with correct signature self.write_tx_input_witness(self.serialized_tx, txi_sign, key_sign_pub, signature) self.set_serialized_signature(i_sign, signature) async def step5_serialize_outputs(self) -> None: pass async def step6_sign_segwit_inputs(self) -> None: pass async def step7_finish(self) -> None: await helpers.request_tx_finish(self.tx_req) def check_prevtx_output(self, txo_bin: PrevOutput) -> None: if txo_bin.decred_script_version != 0: raise wire.ProcessError( "Cannot use utxo that has script_version != 0") @staticmethod def write_tx_input( w: writers.Writer, txi: TxInput | PrevInput, script: bytes, ) -> None: writers.write_bytes_reversed(w, txi.prev_hash, writers.TX_HASH_SIZE) writers.write_uint32(w, txi.prev_index or 0) writers.write_uint8(w, txi.decred_tree or 0) writers.write_uint32(w, txi.sequence) @staticmethod def write_tx_output( w: writers.Writer, txo: TxOutput | PrevOutput, script_pubkey: bytes, ) -> None: writers.write_uint64(w, txo.amount) if PrevOutput.is_type_of(txo): if txo.decred_script_version is None: raise wire.DataError("Script version must be provided") writers.write_uint16(w, txo.decred_script_version) else: writers.write_uint16(w, DECRED_SCRIPT_VERSION) writers.write_bytes_prefixed(w, script_pubkey) def process_sstx_commitment_owned(self, txo: TxOutput) -> bytearray: if not self.tx_info.output_is_change(txo): raise wire.DataError("Invalid sstxcommitment path.") node = self.keychain.derive(txo.address_n) pkh = ecdsa_hash_pubkey(node.public_key(), self.coin) op_return_data = scripts_decred.sstxcommitment_pkh(pkh, txo.amount) txo.amount = 0 # Clear the amount, since this is an OP_RETURN. return scripts_decred.output_script_paytoopreturn(op_return_data) async def approve_staking_ticket(self) -> None: assert isinstance(self.approver, DecredApprover) if self.tx_info.tx.outputs_count != 3: raise wire.DataError("Ticket has wrong number of outputs.") # SSTX submission txo = await helpers.request_tx_output(self.tx_req, 0, self.coin) if txo.address is None: raise wire.DataError("Missing address.") script_pubkey = scripts_decred.output_script_sstxsubmissionpkh( txo.address) await self.approver.add_decred_sstx_submission(txo, script_pubkey) self.tx_info.add_output(txo, script_pubkey) self.write_tx_output(self.serialized_tx, txo, script_pubkey) # SSTX commitment txo = await helpers.request_tx_output(self.tx_req, 1, self.coin) if txo.amount != self.approver.total_in: raise wire.DataError("Wrong sstxcommitment amount.") script_pubkey = self.process_sstx_commitment_owned(txo) self.approver.add_change_output(txo, script_pubkey) self.tx_info.add_output(txo, script_pubkey) self.write_tx_output(self.serialized_tx, txo, script_pubkey) # SSTX change txo = await helpers.request_tx_output(self.tx_req, 2, self.coin) if txo.address is None: raise wire.DataError("Missing address.") script_pubkey = scripts_decred.output_script_sstxchange(txo.address) # Using change addresses is no longer common practice. Inputs are split # beforehand and should be exact. SSTX change should pay zero amount to # a zeroed hash. if txo.amount != 0: raise wire.DataError("Only value of 0 allowed for sstx change.") if script_pubkey != OUTPUT_SCRIPT_NULL_SSTXCHANGE: raise wire.DataError( "Only zeroed addresses accepted for sstx change.") self.approver.add_change_output(txo, script_pubkey) self.tx_info.add_output(txo, script_pubkey) self.write_tx_output(self.serialized_tx, txo, script_pubkey) def write_tx_header( self, w: writers.Writer, tx: SignTx | PrevTx, witness_marker: bool, ) -> None: # The upper 16 bits of the transaction version specify the serialization # format and the lower 16 bits specify the version number. if witness_marker: version = tx.version | DECRED_SERIALIZE_FULL else: version = tx.version | DECRED_SERIALIZE_NO_WITNESS writers.write_uint32(w, version) def write_tx_footer(self, w: writers.Writer, tx: SignTx | PrevTx) -> None: assert tx.expiry is not None # checked in sanitize_* writers.write_uint32(w, tx.lock_time) writers.write_uint32(w, tx.expiry) def write_tx_input_witness(self, w: writers.Writer, txi: TxInput, pubkey: bytes, signature: bytes) -> None: writers.write_uint64(w, txi.amount) writers.write_uint32(w, 0) # block height fraud proof writers.write_uint32(w, 0xFFFF_FFFF) # block index fraud proof scripts_decred.write_input_script_prefixed( w, txi.script_type, txi.multisig, self.coin, self.get_sighash_type(txi), pubkey, signature, )
def address_multisig_p2wsh_in_p2sh(pubkeys: list[bytes], m: int, coin: CoinInfo) -> str: if coin.address_type_p2sh is None: raise wire.ProcessError("Multisig not enabled on this coin") witness_script_h = HashWriter(sha256()) write_output_script_multisig(witness_script_h, pubkeys, m) return address_p2wsh_in_p2sh(witness_script_h.get_digest(), coin)