I have been trying to wrap my head around this for a while to no avail, so please help me understand.
So far, I have been able to use curl to make RPC calls to Infura to get account balances, and that works perfectly. But according to this answer, signing and sending transactions purely through Infura is impossible. I have done a lot of research to try and figure this out, and I think I must not be understanding something on a conceptual level about how signing a transaction actually works.
Ordinarily, the user would hold their ETH in a Meta Mask wallet (or something similar). Then the front-end code would either use web3.js or web3.py to ask Meta Mask to sign a transaction or send ETH to a smart contract. I assume this works because Meta Mask has access to the keystore file from which it can derive the private key. Meta Mask is just a browser extension, and so that means the private key never leaves the user's computer. That, and because the project is open-source, is what gives users the trust to use it to store their funds. Users know for sure that their private key isn't being sent somewhere else to be stolen. So the signing is done locally, and then the transaction hash is sent along with the transaction to Infura, which then carries out the transaction.
However, isn't there a problem here? Looking at this answer again, it implies that you cannot sign a transaction without spinning up a local Ethereum node such as geth. Obviously, Meta Mask is not doing that. I know I'm not running an Ethereum node because I still have plenty of hard drive space. So how exactly is Meta Mask signing the transaction? I have the same exact question regarding web3.py, because I am able to do the exact same thing using it, even though I am not running a local node. And this answer adds to my confusion, as it indicates that web3.py is doing the exact same thing that Meta Mask is doing, both of which connect to the blockchain using Infura. Why then, is web3.py able to sign a transaction, when a direct RPC call to Infura's API in C++ is not able to do so?
Furthermore, the Infura docs do list "eth_signTransaction" as an endpoint but it does not take a private key as a parameter. That's fine, but then this is the code I call in web3.py:
signed_tx = web3.eth.account.sign_transaction(transaction, private_key)
So web3.py requires the private key in order to sign the transaction (where the transaction is just a JSON object and the private key is a string). So what goes on inside of web3.py? I've tried to look but the code is confusing. Despite Meta Mask and web3.py being open source, there's no easy way to tell what is going on inside of that code to actually sign the transaction. Does this mean I basically need to create my own version of web3.py (web3.cpp?) in order to accomplish my goal?
The only thing I can think of is that somehow it must be possible to make an RPC call to Infura's API in order to sign a transaction. But that doesn't make any sense, because then you are exposing the private key, which is exactly why Infura would disable that kind of call... So Infura disables it, which means web3.py and Meta Mask shouldn't be able to call it, but they can... But if they're calling a function that exposes private keys, why would anyone use these services? ...It's an endless back-and-forth of not understanding what is going on.
Possibly the simplest way to phrase this question: How can the signing and sending of transactions take place within Meta Mask if the user is not running a local node?
My goal is to create an application that users trust they can use to store their funds. Ideally, it would probably be best to connect it to Meta Mask, but I don't know how you would go about connecting a desktop app to a browser extension. So the alternative is to just store the private keys in the desktop app (on the user's computer) almost like it's its own wallet, and then make the app open-source so that people can trust what it does with the keystore file. Is this the right approach, or am I missing something here as well?