def sanitize_tx_input(txi: TxInput, coin: CoinInfo) -> TxInput: if len(txi.prev_hash) != TX_HASH_SIZE: raise wire.DataError("Provided prev_hash is invalid.") if txi.multisig and txi.script_type not in common.MULTISIG_INPUT_SCRIPT_TYPES: raise wire.DataError("Multisig field provided but not expected.") if not txi.multisig and txi.script_type == InputScriptType.SPENDMULTISIG: raise wire.DataError("Multisig details required.") if txi.script_type in common.INTERNAL_INPUT_SCRIPT_TYPES: if not txi.address_n: raise wire.DataError("Missing address_n field.") if txi.script_pubkey: raise wire.DataError( "Input's script_pubkey provided but not expected.") else: if txi.address_n: raise wire.DataError( "Input's address_n provided but not expected.") if not txi.script_pubkey: raise wire.DataError("Missing script_pubkey field.") if not coin.decred and txi.decred_tree is not None: raise wire.DataError( "Decred details provided but Decred coin not specified.") if txi.script_type in common.SEGWIT_INPUT_SCRIPT_TYPES or txi.witness is not None: if not coin.segwit: raise wire.DataError("Segwit not enabled on this coin.") if txi.script_type == InputScriptType.SPENDTAPROOT and not coin.taproot: raise wire.DataError("Taproot not enabled on this coin") if txi.commitment_data and not txi.ownership_proof: raise wire.DataError( "commitment_data field provided but not expected.") if txi.orig_hash and txi.orig_index is None: raise wire.DataError("Missing orig_index field.") return txi
async def sign_tx(ctx, msg: NEMSignTx, keychain): validate(msg) await validate_path( ctx, keychain, msg.transaction.address_n, check_path(msg.transaction.address_n, msg.transaction.network), ) node = keychain.derive(msg.transaction.address_n) if msg.multisig: public_key = msg.multisig.signer common = msg.multisig await multisig.ask(ctx, msg) else: public_key = seed.remove_ed25519_prefix(node.public_key()) common = msg.transaction if msg.transfer: tx = await transfer.transfer(ctx, public_key, common, msg.transfer, node) elif msg.provision_namespace: tx = await namespace.namespace(ctx, public_key, common, msg.provision_namespace) elif msg.mosaic_creation: tx = await mosaic.mosaic_creation(ctx, public_key, common, msg.mosaic_creation) elif msg.supply_change: tx = await mosaic.supply_change(ctx, public_key, common, msg.supply_change) elif msg.aggregate_modification: tx = await multisig.aggregate_modification( ctx, public_key, common, msg.aggregate_modification, msg.multisig is not None, ) elif msg.importance_transfer: tx = await transfer.importance_transfer(ctx, public_key, common, msg.importance_transfer) else: raise wire.DataError("No transaction provided") if msg.multisig: # wrap transaction in multisig wrapper if msg.cosigning: tx = multisig.cosign( seed.remove_ed25519_prefix(node.public_key()), msg.transaction, tx, msg.multisig.signer, ) else: tx = multisig.initiate( seed.remove_ed25519_prefix(node.public_key()), msg.transaction, tx) signature = ed25519.sign(node.private_key(), tx, NEM_HASH_ALG) return NEMSignedTx( data=tx, signature=signature, )
async def sign_tx(ctx, msg, keychain): await paths.validate_path(ctx, keychain, msg.address_n) node = keychain.derive(msg.address_n) if msg.transaction is not None: # if the tranasction oprtation is used to execute code on a smart contract if msg.transaction.parameters_manager is not None: parameters_manager = msg.transaction.parameters_manager # operation to delegate from a smart contract with manager.tz if parameters_manager.set_delegate is not None: delegate = _get_address_by_tag(parameters_manager.set_delegate) await layout.require_confirm_delegation_baker(ctx, delegate) await layout.require_confirm_set_delegate(ctx, msg.transaction.fee) # operation to remove delegate from the smart contract with manager.tz elif parameters_manager.cancel_delegate is not None: address = _get_address_from_contract(msg.transaction.destination) await layout.require_confirm_delegation_manager_withdraw(ctx, address) await layout.require_confirm_manager_remove_delegate( ctx, msg.transaction.fee ) # operation to transfer tokens from a smart contract to an implicit account or a smart contract elif parameters_manager.transfer is not None: to = _get_address_from_contract(parameters_manager.transfer.destination) await layout.require_confirm_tx( ctx, to, parameters_manager.transfer.amount ) await layout.require_confirm_fee( ctx, parameters_manager.transfer.amount, msg.transaction.fee ) else: # transactions from an implicit account to = _get_address_from_contract(msg.transaction.destination) await layout.require_confirm_tx(ctx, to, msg.transaction.amount) await layout.require_confirm_fee( ctx, msg.transaction.amount, msg.transaction.fee ) elif msg.origination is not None: source = _get_address_by_tag(msg.origination.source) await layout.require_confirm_origination(ctx, source) # if we are immediately delegating contract if msg.origination.delegate is not None: delegate = _get_address_by_tag(msg.origination.delegate) await layout.require_confirm_delegation_baker(ctx, delegate) await layout.require_confirm_origination_fee( ctx, msg.origination.balance, msg.origination.fee ) elif msg.delegation is not None: source = _get_address_by_tag(msg.delegation.source) delegate = None if msg.delegation.delegate is not None: delegate = _get_address_by_tag(msg.delegation.delegate) if delegate is not None and source != delegate: await layout.require_confirm_delegation_baker(ctx, delegate) await layout.require_confirm_set_delegate(ctx, msg.delegation.fee) # if account registers itself as a delegate else: await layout.require_confirm_register_delegate( ctx, source, msg.delegation.fee ) elif msg.proposal is not None: proposed_protocols = [_get_protocol_hash(p) for p in msg.proposal.proposals] await layout.require_confirm_proposals(ctx, proposed_protocols) elif msg.ballot is not None: proposed_protocol = _get_protocol_hash(msg.ballot.proposal) submitted_ballot = _get_ballot(msg.ballot.ballot) await layout.require_confirm_ballot(ctx, proposed_protocol, submitted_ballot) else: raise wire.DataError("Invalid operation") w = bytearray() _get_operation_bytes(w, msg) opbytes = bytes(w) # watermark 0x03 is prefix for transactions, delegations, originations, reveals... watermark = bytes([3]) wm_opbytes = watermark + opbytes wm_opbytes_hash = hashlib.blake2b(wm_opbytes, outlen=32).digest() signature = ed25519.sign(node.private_key(), wm_opbytes_hash) sig_op_contents = opbytes + signature sig_op_contents_hash = hashlib.blake2b(sig_op_contents, outlen=32).digest() ophash = helpers.base58_encode_check(sig_op_contents_hash, prefix="o") sig_prefixed = helpers.base58_encode_check( signature, prefix=helpers.TEZOS_SIGNATURE_PREFIX ) return TezosSignedTx( signature=sig_prefixed, sig_op_contents=sig_op_contents, operation_hash=ophash )
async def sign_tx(ctx, msg, keychain): msg = sanitize(msg) #TODO refine `sanitize` to support more fields check(msg) #TODO refine check to support ~ await paths.validate_path(ctx, validate_full_path, keychain, msg.address_n, CURVE) node = keychain.derive(msg.address_n) seckey = node.private_key() public_key = secp256k1.publickey(seckey, False) # uncompressed sender_address = sha3_256(public_key[1:], keccak=True).digest()[12:] recipient = address.bytes_from_address(msg.to) # TODO- check sender and real sender addr value = int.from_bytes(msg.value, "big") await require_confirm_tx(ctx, recipient, value, msg.chain_id, None, msg.tx_type) # TODO if fee delegation tx? -> require_confirm_fee_delegation await require_confirm_fee( ctx, value, int.from_bytes(msg.gas_price, "big"), int.from_bytes(msg.gas_limit, "big"), msg.chain_id, msg.fee_ratio, None, msg.tx_type, ) data_total = msg.data_length data = bytearray() data += msg.data_initial_chunk data_left = data_total - len(msg.data_initial_chunk) total_length = get_total_length(msg, data_total) print("total length: ", total_length) sha = HashWriter(sha3_256(keccak=True)) if msg.tx_type is None: sha.extend(rlp.encode_length(total_length, True)) # total length for field in (msg.nonce, msg.gas_price, msg.gas_limit, recipient, 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)) if msg.chain_id: sha.extend(rlp.encode(msg.chain_id)) sha.extend(rlp.encode(0)) sha.extend(rlp.encode(0)) else: basic_type = to_basic_type(msg.tx_type) attributes = [msg.tx_type, msg.nonce, msg.gas_price, msg.gas_limit] # TxTypeValueTransfer(0x08) if basic_type == 0x08: attributes += [recipient, msg.value, sender_address] if to_fee_type(msg.tx_type) == TX_TYPE_PARTIAL_FEE_DELEGATION: attributes.append(msg.fee_ratio) # TxTypeValueTransferMemo(0x10), TxTypeSmartContractExecution(0x30) elif basic_type == 0x10 or basic_type == 0x30: attributes += [recipient, msg.value, sender_address, data] if to_fee_type(msg.tx_type) == TX_TYPE_PARTIAL_FEE_DELEGATION: attributes.append(msg.fee_ratio) # TxTypeSmartContractDeploy(0x28) elif basic_type == 0x28: human_readable = 0x00 if msg.human_readable: human_readable = 0x01 attributes += [ recipient, msg.value, sender_address, data, human_readable ] if to_fee_type(msg.tx_type) == TX_TYPE_PARTIAL_FEE_DELEGATION: attributes.append(msg.fee_ratio) attributes.append(msg.code_format) # TxTypeCancel(0x38) elif basic_type == 0x38: attributes.append(sender_address) if to_fee_type(msg.tx_type) == TX_TYPE_PARTIAL_FEE_DELEGATION: attributes.append(msg.fee_ratio) # not supported tx type else: raise wire.DataError("Not supported transaction type") encoded_out = rlp.encode(attributes) sha.extend(rlp.encode([encoded_out, msg.chain_id, 0, 0], True)) digest = sha.get_digest() result = sign_digest(msg, keychain, digest) return result
async def sign_tx_dispatch(state, msg, keychain): if msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInitRequest: from apps.monero.signing import step_01_init_transaction return ( await step_01_init_transaction.init_transaction( state, msg.address_n, msg.network_type, msg.tsx_data, keychain ), (MessageType.MoneroTransactionSetInputRequest,), ) elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionSetInputRequest: from apps.monero.signing import step_02_set_input return ( await step_02_set_input.set_input(state, msg.src_entr), ( MessageType.MoneroTransactionSetInputRequest, MessageType.MoneroTransactionInputsPermutationRequest, MessageType.MoneroTransactionInputViniRequest, ), ) elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInputsPermutationRequest: from apps.monero.signing import step_03_inputs_permutation return ( await step_03_inputs_permutation.tsx_inputs_permutation(state, msg.perm), (MessageType.MoneroTransactionInputViniRequest,), ) elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInputViniRequest: from apps.monero.signing import step_04_input_vini return ( await step_04_input_vini.input_vini( state, msg.src_entr, msg.vini, msg.vini_hmac, msg.orig_idx ), ( MessageType.MoneroTransactionInputViniRequest, MessageType.MoneroTransactionAllInputsSetRequest, ), ) elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionAllInputsSetRequest: from apps.monero.signing import step_05_all_inputs_set return ( await step_05_all_inputs_set.all_inputs_set(state), (MessageType.MoneroTransactionSetOutputRequest,), ) elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionSetOutputRequest: from apps.monero.signing import step_06_set_output is_offloaded_bp = bool(msg.is_offloaded_bp) dst, dst_hmac, rsig_data = msg.dst_entr, msg.dst_entr_hmac, msg.rsig_data del msg return ( await step_06_set_output.set_output( state, dst, dst_hmac, rsig_data, is_offloaded_bp ), ( MessageType.MoneroTransactionSetOutputRequest, MessageType.MoneroTransactionAllOutSetRequest, ), ) elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionAllOutSetRequest: from apps.monero.signing import step_07_all_outputs_set return ( await step_07_all_outputs_set.all_outputs_set(state), (MessageType.MoneroTransactionSignInputRequest,), ) elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionSignInputRequest: from apps.monero.signing import step_09_sign_input return ( await step_09_sign_input.sign_input( state, msg.src_entr, msg.vini, msg.vini_hmac, msg.pseudo_out, msg.pseudo_out_hmac, msg.pseudo_out_alpha, msg.spend_key, msg.orig_idx, ), ( MessageType.MoneroTransactionSignInputRequest, MessageType.MoneroTransactionFinalRequest, ), ) elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionFinalRequest: from apps.monero.signing import step_10_sign_final return await step_10_sign_final.final_msg(state), None else: raise wire.DataError("Unknown message")
def __init__( self, script_pubkey: bytes, script_sig: bytes | None, witness: bytes | None, coin: CoinInfo, ): self.threshold = 1 self.public_keys: list[bytes] = [] self.signatures: list[tuple[bytes, int]] = [] if not script_sig: if not witness: raise wire.DataError("Signature data not provided") if len(script_pubkey) == 22: # P2WPKH public_key, signature, hash_type = parse_witness_p2wpkh( witness) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) if output_script_native_p2wpkh_or_p2wsh( pubkey_hash) != script_pubkey: raise wire.DataError("Invalid public key hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_pubkey) == 34: # P2WSH script, self.signatures = parse_witness_multisig(witness) script_hash = sha256(script).digest() if output_script_native_p2wpkh_or_p2wsh( script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig( script) else: raise wire.DataError("Unsupported signature script") elif witness and witness != b"\x00": if len(script_sig) == 23: # P2WPKH nested in BIP16 P2SH public_key, signature, hash_type = parse_witness_p2wpkh( witness) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) if input_script_p2wpkh_in_p2sh(pubkey_hash) != script_sig: raise wire.DataError("Invalid public key hash") script_hash = coin.script_hash(script_sig[1:]) if output_script_p2sh(script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_sig) == 35: # P2WSH nested in BIP16 P2SH script, self.signatures = parse_witness_multisig(witness) script_hash = sha256(script).digest() if input_script_p2wsh_in_p2sh(script_hash) != script_sig: raise wire.DataError("Invalid script hash") script_hash = coin.script_hash(script_sig[1:]) if output_script_p2sh(script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig( script) else: raise wire.DataError("Unsupported signature script") else: if len(script_pubkey) == 25: # P2PKH public_key, signature, hash_type = parse_input_script_p2pkh( script_sig) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) if output_script_p2pkh(pubkey_hash) != script_pubkey: raise wire.DataError("Invalid public key hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_pubkey) == 23: # P2SH script, self.signatures = parse_input_script_multisig( script_sig) script_hash = coin.script_hash(script) if output_script_p2sh(script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig( script) else: raise wire.DataError("Unsupported signature script") if self.threshold != len(self.signatures): raise wire.DataError("Invalid signature")
async def get_ownership_proof( ctx: wire.Context, msg: GetOwnershipProof, keychain: Keychain, coin: CoinInfo, authorization: CoinJoinAuthorization | None = None, ) -> OwnershipProof: if authorization: if not authorization.check_get_ownership_proof(msg): raise wire.ProcessError("Unauthorized operation") else: await validate_path( ctx, keychain, msg.address_n, validate_path_against_script_type(coin, msg), ) if msg.script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES: raise wire.DataError("Invalid script type") if msg.script_type in common.SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit: raise wire.DataError("Segwit not enabled on this coin") node = keychain.derive(msg.address_n) address = addresses.get_address(msg.script_type, coin, node, msg.multisig) script_pubkey = scripts.output_derive_script(address, coin) ownership_id = get_identifier(script_pubkey, keychain) # If the scriptPubKey is multisig, then the caller has to provide # ownership IDs, otherwise providing an ID is optional. if msg.multisig: if ownership_id not in msg.ownership_ids: raise wire.DataError("Missing ownership identifier") elif msg.ownership_ids: if msg.ownership_ids != [ownership_id]: raise wire.DataError("Invalid ownership identifier") else: msg.ownership_ids = [ownership_id] # In order to set the "user confirmation" bit in the proof, the user must actually confirm. if msg.user_confirmation and not authorization: await confirm_action( ctx, "confirm_ownership_proof", title="Proof of ownership", description="Do you want to create a proof of ownership?", ) if msg.commitment_data: await confirm_blob( ctx, "confirm_ownership_proof", title="Proof of ownership", description="Commitment data:", data=msg.commitment_data, icon=ui.ICON_CONFIG, icon_color=ui.ORANGE_ICON, ) ownership_proof, signature = generate_proof( node, msg.script_type, msg.multisig, coin, msg.user_confirmation, msg.ownership_ids, script_pubkey, msg.commitment_data, ) return OwnershipProof(ownership_proof=ownership_proof, signature=signature)
def encode_address(tag: int, S: bcncrypto.BcnPoint, Sv: bcncrypto.BcnPoint): if tag == 0: return bcncrypto.encode_address(6, S, Sv) if tag == 1: return bcncrypto.encode_address(572238, S, Sv) raise wire.DataError("Unknown address type")
def add_amount(sum: int, amount: int): if amount > 0xFFFFFFFFFFFFFFFF - sum: # sum is safe, amount itself can be > 2^64-1 raise wire.DataError("Amount overflow") sum += amount return sum
def _add_output(self, txo: TxOutput, script_pubkey: bytes) -> None: super()._add_output(txo, script_pubkey) # All CoinJoin outputs must be accompanied by a signed payment request. if txo.payment_req_index is None: raise wire.DataError("Missing payment request.")
async def get_ownership_proof( ctx, msg: GetOwnershipProof, keychain: Keychain, coin: coininfo.CoinInfo ) -> OwnershipProof: await validate_path( ctx, addresses.validate_full_path, keychain, msg.address_n, coin.curve_name, coin=coin, script_type=msg.script_type, ) if msg.script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES: raise wire.DataError("Invalid script type") if msg.script_type in common.SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit: raise wire.DataError("Segwit not enabled on this coin") node = keychain.derive(msg.address_n) address = addresses.get_address(msg.script_type, coin, node, msg.multisig) script_pubkey = scripts.output_derive_script(address, coin) ownership_id = get_identifier(script_pubkey, keychain) # If the scriptPubKey is multisig, then the caller has to provide # ownership IDs, otherwise providing an ID is optional. if msg.multisig: if ownership_id not in msg.ownership_ids: raise wire.DataError("Missing ownership identifier") elif msg.ownership_ids: if msg.ownership_ids != [ownership_id]: raise wire.DataError("Invalid ownership identifier") else: msg.ownership_ids = [ownership_id] # In order to set the "user confirmation" bit in the proof, the user must actually confirm. if msg.user_confirmation: text = Text("Proof of ownership", ui.ICON_CONFIG) text.normal("Do you want to create a") if not msg.commitment_data: text.normal("proof of ownership?") else: hex_data = hexlify(msg.commitment_data).decode() text.normal("proof of ownership for:") if len(hex_data) > 3 * _MAX_MONO_LINE: text.mono(hex_data[0:_MAX_MONO_LINE]) text.mono( hex_data[_MAX_MONO_LINE : 3 * _MAX_MONO_LINE // 2 - 1] + "..." + hex_data[-3 * _MAX_MONO_LINE // 2 + 2 : -_MAX_MONO_LINE] ) text.mono(hex_data[-_MAX_MONO_LINE:]) else: text.mono(hex_data) await require_confirm(ctx, text) ownership_proof, signature = generate_proof( node, msg.script_type, msg.multisig, coin, msg.user_confirmation, msg.ownership_ids, script_pubkey, msg.commitment_data, ) return OwnershipProof(ownership_proof=ownership_proof, signature=signature)
async def process_external_input(self, txi: TxInput) -> None: raise wire.DataError("External inputs not supported")
def hash143_preimage_hash(self, txi: TxInputType, public_keys: List[bytes], threshold: int) -> 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_sighash_type(txi)) 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_sighash_type(txi)) 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, public_keys, threshold, self.coin) 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)
def sanitize_tx_output(txo: TxOutput, coin: CoinInfo) -> TxOutput: if txo.multisig and txo.script_type not in common.MULTISIG_OUTPUT_SCRIPT_TYPES: raise wire.DataError("Multisig field provided but not expected.") if not txo.multisig and txo.script_type == OutputScriptType.PAYTOMULTISIG: raise wire.DataError("Multisig details required.") if txo.address_n and txo.script_type not in common.CHANGE_OUTPUT_SCRIPT_TYPES: raise wire.DataError("Output's address_n provided but not expected.") if txo.amount is None: raise wire.DataError("Missing amount field.") if txo.script_type in common.SEGWIT_OUTPUT_SCRIPT_TYPES: if not coin.segwit: raise wire.DataError("Segwit not enabled on this coin.") if txo.script_type == OutputScriptType.PAYTOTAPROOT and not coin.taproot: raise wire.DataError("Taproot not enabled on this coin") if txo.script_type == OutputScriptType.PAYTOOPRETURN: # op_return output if txo.op_return_data is None: raise wire.DataError("OP_RETURN output without op_return_data") if txo.amount != 0: raise wire.DataError("OP_RETURN output with non-zero amount") if txo.address or txo.address_n or txo.multisig: raise wire.DataError("OP_RETURN output with address or multisig") else: if txo.op_return_data: raise wire.DataError( "OP RETURN data provided but not OP RETURN script type.") if txo.address_n and txo.address: raise wire.DataError("Both address and address_n provided.") if not txo.address_n and not txo.address: raise wire.DataError("Missing address") if txo.orig_hash and txo.orig_index is None: raise wire.DataError("Missing orig_index field.") return txo
def verify_path(self, path: Bip32Path) -> None: if not self.is_in_keychain(path): raise wire.DataError("Forbidden key path")
async def sign_tx(ctx, msg, keychain): await paths.validate_path(ctx, helpers.validate_full_path, path=msg.address_n) node = keychain.derive(msg.address_n, helpers.TEZOS_CURVE) if msg.transaction is not None: to = _get_address_from_contract(msg.transaction.destination) await layout.require_confirm_tx(ctx, to, msg.transaction.amount) await layout.require_confirm_fee(ctx, msg.transaction.amount, msg.transaction.fee) elif msg.origination is not None: source = _get_address_from_contract(msg.origination.source) await layout.require_confirm_origination(ctx, source) # if we are immediately delegating contract if msg.origination.delegate is not None: delegate = _get_address_by_tag(msg.origination.delegate) await layout.require_confirm_delegation_baker(ctx, delegate) await layout.require_confirm_origination_fee(ctx, msg.origination.balance, msg.origination.fee) elif msg.delegation is not None: source = _get_address_from_contract(msg.delegation.source) delegate = None if msg.delegation.delegate is not None: delegate = _get_address_by_tag(msg.delegation.delegate) if delegate is not None and source != delegate: await layout.require_confirm_delegation_baker(ctx, delegate) await layout.require_confirm_set_delegate(ctx, msg.delegation.fee) # if account registers itself as a delegate else: await layout.require_confirm_register_delegate( ctx, source, msg.delegation.fee) else: raise wire.DataError("Invalid operation") w = bytearray() _get_operation_bytes(w, msg) opbytes = bytes(w) # watermark 0x03 is prefix for transactions, delegations, originations, reveals... watermark = bytes([3]) wm_opbytes = watermark + opbytes wm_opbytes_hash = hashlib.blake2b(wm_opbytes, outlen=32).digest() signature = ed25519.sign(node.private_key(), wm_opbytes_hash) sig_op_contents = opbytes + signature sig_op_contents_hash = hashlib.blake2b(sig_op_contents, outlen=32).digest() ophash = helpers.base58_encode_check(sig_op_contents_hash, prefix="o") sig_prefixed = helpers.base58_encode_check( signature, prefix=helpers.TEZOS_SIGNATURE_PREFIX) return TezosSignedTx(signature=sig_prefixed, sig_op_contents=sig_op_contents, operation_hash=ophash)
def ensure_hash_type(self, hash_type: int) -> None: if any(h != hash_type for _, h in self.signatures): raise wire.DataError("Unsupported sighash type")
async def _fail_or_warn_path(ctx: wire.Context, path: list[int], path_name: str) -> None: if safety_checks.is_strict(): raise wire.DataError(f"Invalid {path_name.lower()}") else: await show_warning_path(ctx, path, path_name)
async def approve_tx(self, tx_info: TxInfo, orig_txs: list[OriginalTxInfo]) -> None: fee = self.total_in - self.total_out # some coins require negative fees for reward TX if fee < 0 and not self.coin.negative_fee: raise wire.NotEnoughFunds("Not enough funds") total = self.total_in - self.change_out spending = total - self.external_in # fee_threshold = (coin.maxfee per byte * tx size) fee_threshold = (self.coin.maxfee_kb / 1000) * (self.weight.get_total() / 4) # fee > (coin.maxfee per byte * tx size) if fee > fee_threshold: if fee > 10 * fee_threshold and safety_checks.is_strict(): raise wire.DataError("The fee is unexpectedly large") await helpers.confirm_feeoverthreshold(fee, self.coin, self.amount_unit) if self.change_count > self.MAX_SILENT_CHANGE_COUNT: await helpers.confirm_change_count_over_threshold(self.change_count) if orig_txs: # Replacement transaction. orig_spending = ( self.orig_total_in - self.orig_change_out - self.orig_external_in ) orig_fee = self.orig_total_in - self.orig_total_out if fee < 0 or orig_fee < 0: raise wire.ProcessError( "Negative fees not supported in transaction replacement." ) # Replacement transactions are only allowed to make amendments which # do not increase the amount that we are spending on external outputs. # In other words, the total amount being sent out of the wallet must # not increase by more than the fee difference (so additional funds # can only go towards the fee, which is confirmed by the user). if spending - orig_spending > fee - orig_fee: raise wire.ProcessError("Invalid replacement transaction.") # Replacement transactions must not change the effective nLockTime. lock_time = 0 if tx_info.lock_time_disabled() else tx_info.tx.lock_time for orig in orig_txs: orig_lock_time = 0 if orig.lock_time_disabled() else orig.tx.lock_time if lock_time != orig_lock_time: raise wire.ProcessError( "Original transactions must have same effective nLockTime as replacement transaction." ) if not self.is_payjoin(): # Not a PayJoin: Show the actual fee difference, since any difference in the fee is # coming entirely from the user's own funds and from decreases of external outputs. # We consider the decreases as belonging to the user. await helpers.confirm_modify_fee( fee - orig_fee, fee, self.coin, self.amount_unit ) elif spending > orig_spending: # PayJoin and user is spending more: Show the increase in the user's contribution # to the fee, ignoring any contribution from external inputs. Decreasing of # external outputs is not allowed in PayJoin, so there is no need to handle those. await helpers.confirm_modify_fee( spending - orig_spending, fee, self.coin, self.amount_unit ) else: # PayJoin and user is not spending more: When new external inputs are involved and # the user is paying less, the scenario can be open to multiple interpretations and # the dialog would likely cause more confusion than what it's worth, see PR #1292. pass else: # Standard transaction. if tx_info.tx.lock_time > 0: await helpers.confirm_nondefault_locktime( tx_info.tx.lock_time, tx_info.lock_time_disabled() ) if not self.external_in: await helpers.confirm_total(total, fee, self.coin, self.amount_unit) else: await helpers.confirm_joint_total( spending, total, self.coin, self.amount_unit )
async def process_internal_input(self, txi: TxInput) -> None: if txi.script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES: raise wire.DataError("Wrong input script type") await self.approver.add_internal_input(txi)
async def apply_settings(ctx: wire.Context, msg: ApplySettings) -> Success: if not storage.device.is_initialized(): raise wire.NotInitialized("Device is not initialized") if (msg.homescreen is None and msg.label is None and msg.use_passphrase is None and msg.passphrase_always_on_device is None and msg.display_rotation is None and msg.auto_lock_delay_ms is None and msg.safety_checks is None and msg.experimental_features is None): raise wire.ProcessError("No setting provided") if msg.homescreen is not None: validate_homescreen(msg.homescreen) await require_confirm_change_homescreen(ctx) try: storage.device.set_homescreen(msg.homescreen) except ValueError: raise wire.DataError("Invalid homescreen") if msg.label is not None: if len(msg.label) > storage.device.LABEL_MAXLENGTH: raise wire.DataError("Label too long") await require_confirm_change_label(ctx, msg.label) storage.device.set_label(msg.label) if msg.use_passphrase is not None: await require_confirm_change_passphrase(ctx, msg.use_passphrase) storage.device.set_passphrase_enabled(msg.use_passphrase) if msg.passphrase_always_on_device is not None: if not storage.device.is_passphrase_enabled(): raise wire.DataError("Passphrase is not enabled") await require_confirm_change_passphrase_source( ctx, msg.passphrase_always_on_device) storage.device.set_passphrase_always_on_device( msg.passphrase_always_on_device) if msg.auto_lock_delay_ms is not None: if msg.auto_lock_delay_ms < storage.device.AUTOLOCK_DELAY_MINIMUM: raise wire.ProcessError("Auto-lock delay too short") if msg.auto_lock_delay_ms > storage.device.AUTOLOCK_DELAY_MAXIMUM: raise wire.ProcessError("Auto-lock delay too long") await require_confirm_change_autolock_delay(ctx, msg.auto_lock_delay_ms) storage.device.set_autolock_delay_ms(msg.auto_lock_delay_ms) if msg.safety_checks is not None: await require_confirm_safety_checks(ctx, msg.safety_checks) safety_checks.apply_setting(msg.safety_checks) if msg.display_rotation is not None: await require_confirm_change_display_rotation(ctx, msg.display_rotation) storage.device.set_rotation(msg.display_rotation) if msg.experimental_features is not None: await require_confirm_experimental_features(ctx, msg.experimental_features) storage.device.set_experimental_features(msg.experimental_features) reload_settings_from_storage() return Success(message="Settings applied")
def verify_path(self, path: Bip32Path) -> None: if not is_byron_path(path) and not is_shelley_path(path): raise wire.DataError("Forbidden key path")
def validate_path(self, checked_path: list, checked_curve: str): if checked_curve != CURVE or checked_path[:2] != SEED_NAMESPACE: raise wire.DataError("Forbidden key path")
def ensure_hash_type(self, sighash_types: Sequence[SigHashType]) -> None: if any(h not in sighash_types for _, h in self.signatures): raise wire.DataError("Unsupported sighash type")
async def approve_tx(self, tx_info: TxInfo, orig_txs: List[OriginalTxInfo]) -> None: fee = self.total_in - self.total_out # some coins require negative fees for reward TX if fee < 0 and not self.coin.negative_fee: raise wire.NotEnoughFunds("Not enough funds") total = self.total_in - self.change_out spending = total - self.external_in # fee_threshold = (coin.maxfee per byte * tx size) fee_threshold = (self.coin.maxfee_kb / 1000) * (self.weight.get_total() / 4) # fee > (coin.maxfee per byte * tx size) if fee > fee_threshold: if fee > 10 * fee_threshold and safety_checks.is_strict(): raise wire.DataError("The fee is unexpectedly large") await helpers.confirm_feeoverthreshold(fee, self.coin) if self.change_count > self.MAX_SILENT_CHANGE_COUNT: await helpers.confirm_change_count_over_threshold(self.change_count) if orig_txs: # Replacement transaction. orig_spending = ( self.orig_total_in - self.orig_change_out - self.orig_external_in ) orig_fee = self.orig_total_in - self.orig_total_out # Replacement transactions are only allowed to make amendments which # do not increase the amount that we are spending on external outputs. # In other words, the total amount being sent out of the wallet must # not increase by more than the fee difference (so additional funds # can only go towards the fee, which is confirmed by the user). if spending - orig_spending > fee - orig_fee: raise wire.ProcessError("Invalid replacement transaction.") # Replacement transactions must not change the effective nLockTime. lock_time = 0 if tx_info.lock_time_disabled() else tx_info.tx.lock_time for orig in orig_txs: orig_lock_time = 0 if orig.lock_time_disabled() else orig.tx.lock_time if lock_time != orig_lock_time: raise wire.ProcessError( "Original transactions must have same effective nLockTime as replacement transaction." ) if self.external_in > self.orig_external_in: description = "PayJoin" elif len(orig_txs) > 1: description = "Transaction meld" else: description = "Fee modification" for orig in orig_txs: await helpers.confirm_replacement(description, orig.orig_hash) # Always ask the user to confirm when they are paying more towards the fee. # If they are not spending more, then ask for confirmation only if it's not # a PayJoin. In complex scenarios where the user is not spending more and # there are new external inputs the scenario can be open to multiple # interpretations and the dialog would likely cause more confusion than # what it's worth, see PR #1292. if spending > orig_spending or self.external_in == self.orig_external_in: await helpers.confirm_modify_fee( spending - orig_spending, fee, self.coin ) else: # Standard transaction. if tx_info.tx.lock_time > 0: await helpers.confirm_nondefault_locktime( tx_info.tx.lock_time, tx_info.lock_time_disabled() ) if not self.external_in: await helpers.confirm_total(total, fee, self.coin) else: await helpers.confirm_joint_total(spending, total, self.coin)
def verify_bip340(self, digest: bytes) -> None: if not bip340.verify(self.public_keys[0], self.signatures[0][0], digest): raise wire.DataError("Invalid signature")
async def get_public_key(ctx: wire.Context, msg: GetPublicKey) -> PublicKey: coin_name = msg.coin_name or "Bitcoin" script_type = msg.script_type or InputScriptType.SPENDADDRESS coin = coininfo.by_name(coin_name) curve_name = msg.ecdsa_curve_name or coin.curve_name keychain = await get_keychain(ctx, curve_name, [paths.AlwaysMatchingSchema]) node = keychain.derive(msg.address_n) if ( script_type in ( InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG, InputScriptType.SPENDTAPROOT, ) and coin.xpub_magic is not None ): node_xpub = node.serialize_public(coin.xpub_magic) elif ( coin.segwit and script_type == InputScriptType.SPENDP2SHWITNESS and (msg.ignore_xpub_magic or coin.xpub_magic_segwit_p2sh is not None) ): # TODO: resolve type: ignore below node_xpub = node.serialize_public( coin.xpub_magic if msg.ignore_xpub_magic else coin.xpub_magic_segwit_p2sh # type: ignore ) elif ( coin.segwit and script_type == InputScriptType.SPENDWITNESS and (msg.ignore_xpub_magic or coin.xpub_magic_segwit_native is not None) ): # TODO: resolve type: ignore below node_xpub = node.serialize_public( coin.xpub_magic if msg.ignore_xpub_magic else coin.xpub_magic_segwit_native # type: ignore ) else: raise wire.DataError("Invalid combination of coin and script_type") pubkey = node.public_key() if pubkey[0] == 1: pubkey = b"\x00" + pubkey[1:] node_type = HDNodeType( depth=node.depth(), child_num=node.child_num(), fingerprint=node.fingerprint(), chain_code=node.chain_code(), public_key=pubkey, ) if msg.show_display: from trezor.ui.layouts import show_xpub await show_xpub(ctx, node_xpub, "XPUB", "Cancel") return PublicKey( node=node_type, xpub=node_xpub, root_fingerprint=keychain.root_fingerprint(), )
def __init__( self, script_pubkey: bytes, script_sig: bytes | None, witness: bytes | None, coin: CoinInfo, ): self.threshold = 1 self.public_keys: list[memoryview] = [] self.signatures: list[tuple[memoryview, SigHashType]] = [] self.is_taproot = False if not script_sig: if not witness: raise wire.DataError("Signature data not provided") if len(script_pubkey) == 22: # P2WPKH public_key, signature, hash_type = parse_witness_p2wpkh( witness) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) if output_script_native_segwit(0, pubkey_hash) != script_pubkey: raise wire.DataError("Invalid public key hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len( script_pubkey) == 34 and script_pubkey[0] == OP_0: # P2WSH script, self.signatures = parse_witness_multisig(witness) script_hash = sha256(script).digest() if output_script_native_segwit(0, script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig( script) elif len(script_pubkey) == 34 and script_pubkey[0] == OP_1: # P2TR self.is_taproot = True self.public_keys = [parse_output_script_p2tr(script_pubkey)] self.signatures = [parse_witness_p2tr(witness)] else: raise wire.DataError("Unsupported signature script") elif witness and witness != b"\x00": if len(script_sig) == 23: # P2WPKH nested in BIP16 P2SH public_key, signature, hash_type = parse_witness_p2wpkh( witness) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) w = utils.empty_bytearray(23) write_input_script_p2wpkh_in_p2sh(w, pubkey_hash) if w != script_sig: raise wire.DataError("Invalid public key hash") script_hash = coin.script_hash(script_sig[1:]).digest() if output_script_p2sh(script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_sig) == 35: # P2WSH nested in BIP16 P2SH script, self.signatures = parse_witness_multisig(witness) script_hash = sha256(script).digest() w = utils.empty_bytearray(35) write_input_script_p2wsh_in_p2sh(w, script_hash) if w != script_sig: raise wire.DataError("Invalid script hash") script_hash = coin.script_hash(script_sig[1:]).digest() if output_script_p2sh(script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig( script) else: raise wire.DataError("Unsupported signature script") else: if len(script_pubkey) == 25: # P2PKH public_key, signature, hash_type = parse_input_script_p2pkh( script_sig) pubkey_hash = ecdsa_hash_pubkey(public_key, coin) if output_script_p2pkh(pubkey_hash) != script_pubkey: raise wire.DataError("Invalid public key hash") self.public_keys = [public_key] self.signatures = [(signature, hash_type)] elif len(script_pubkey) == 23: # P2SH script, self.signatures = parse_input_script_multisig( script_sig) script_hash = coin.script_hash(script).digest() if output_script_p2sh(script_hash) != script_pubkey: raise wire.DataError("Invalid script hash") self.public_keys, self.threshold = parse_output_script_multisig( script) else: raise wire.DataError("Unsupported signature script") if self.threshold != len(self.signatures): raise wire.DataError("Invalid signature")
NodeType = TypeVar("NodeType", bound=NodeProtocol) MsgIn = TypeVar("MsgIn", bound=MessageType) MsgOut = TypeVar("MsgOut", bound=MessageType) Handler = Callable[[wire.Context, MsgIn], Awaitable[MsgOut]] HandlerWithKeychain = Callable[[wire.Context, MsgIn, "Keychain"], Awaitable[MsgOut]] class Deletable(Protocol): def __del__(self) -> None: ... FORBIDDEN_KEY_PATH = wire.DataError("Forbidden key path") class LRUCache: def __init__(self, size: int) -> None: self.size = size self.cache_keys: list[Any] = [] self.cache: dict[Any, Deletable] = {} def insert(self, key: Any, value: Deletable) -> None: if key in self.cache_keys: self.cache_keys.remove(key) self.cache_keys.insert(0, key) self.cache[key] = value if len(self.cache_keys) > self.size:
async def process_original_input(self, txi: TxInput) -> None: raise wire.DataError("Replacement transactions not supported")