Can clients sign transactions that the backend can send?

Can a client sign a transaction in a secure way as to allow the backend (Go SDK or FCL) to send the transaction on their behalf without revealing their private key?

In the spirit of decentralization, it’s most intuitive to have only clients sign and send their own transactions. It’d be terrible to send your private keys to some other unknown server to sign things on your behalf. This is why we leverage wallets like Metamask, Dapper Labs, Blockto, etc. Actually, it also makes me ask how users in the Ethereum ecosystem sign transactions. Are all user transactions sent client-side?

But what if we want to keep computation on the client as thin as possible? We can spare the FE of ~81kb by not using the JS SDK on the FE, not to mention all the other computation work.

In https://discord.com/channels/613813861610684416/621847359894061056/750397233056317653,
@qvvq mentioned how (with Elixir and LiveView) a user can send a signed transaction back to the Elixir backend. Does this then kinda mean there’s a way to pass along some form of signature function?

3 Likes

Yep, this is a totally valid use case!

A backend can’t directly tamper with a transaction once it’s signed – it can really only act as a passthrough to the chain.

However, the backend could launch a MITM attack and lie about the state of a transaction. For example, the server could send a successful transaction to Flow but return a fake error and then ask the client to retry with a new sequence number. They could even trick the user into spending their money twice. Because of this, it’s still important that the client has a trusted relationship with the backend, or better yet, uses a third-party source to fetch transaction results.

It’s common for Ethereum browser apps to communicate directly with an Ethereum node. However, some web wallets now use an intermediate backend service to add a layer of stability or functionality between the application and the blockchain. For example, a service like this could track a user’s transaction history or perform load balancing across multiple nodes. These services are typically part of the wallet infrastructure, not the application itself.

In addition to this, Flow has native support for multi-sig transactions, which allows for more advanced scenarios that may require coordination between multiple signing agents.

For example, a Flow wallet may use a 2FA process that asks a user to sign a transaction on their laptop and their phone. A dapp could send an unsigned transaction to the wallet backend, which in turn would collect signatures from both the laptop and the phone before forwarding the fully signed transaction to the chain.

We’re designing the Flow client-side tooling to be flexible in the way it signs, brokers and sends transactions. This is where FCL comes in! @qvvg thinks a lot about how clients can interact with Flow and his work on FCL is very applicable here.

2 Likes

Educative! Thanks Pete.

I did some exploring in FCL to understand how an unsigned transaction can actually be sent over the network, and how it can remain untamperable. Turns out it’s because the txn values are converted to buffers. Is this correct? @qvvg

It still seems then that the client must still always use FCL as its trusted tool belt. Entirely trusting a server to form the transaction is insecure because, in your words, transactions can’t really be mutated after signing? Or is it possible to have an entirely safe, fully formed payload without any signatures that can be passed back and forth? Eh, really just an edge case because now it just seems best that the client always forms and signs its own transaction.

I see the trade offs then. How much can you trust the server is parallel to how much computation you’d do on the client side.

1 Like

We’re thinking through how to do this securely, without requiring an extension.
In MetaMask, your keys are stored in the extension, and exposed to your client-side code via web3,
so you can sign transactions (or any other data) and submit to Ethereum (or your backend service).

Currently, there is no way to safely store your private key in a browser without using an extension.

You can create keys using the web-crypto API which are not “extractable” and so safe inasmuch as they will never be in your code as cleartext, or exported from storage. This is almost good enough. The only problem is that browser storage can be cleared by the user at any time, so it’s not suitable as the primary storage location for your key.

3 Likes

@dennisdang I spend so much time thinking about this sort of thing, It so nice seeing other people think about it too, and I think you are on the correct track :slight_smile: For everything I am about to say please keep in mind we are very very very early in all this, so we have so far to go, but hopefully you can see the some of the ground work and foundation being laid now.

We very much took the approach that we should send the things to be signed to the Private Keys instead of trying to get the keys to the thing that needs to be signed.

You are correct in that we are creating a buffer. We encode that buffer as a hex value and send it in a json payload we call a signable to the wallets. Internally to FCL and the JS-SDK the signable is what we pass into the signing function, in the case of FCL we then route that signable to the wallets based on config we received during authentication (Identity as Configuration).

In NBA TopShot, if you go to pay for something using FLOW (Dont think this is currently enabled for external people) It actually works very similarly to what you described, the backend produces everything the transaction needs (it then slots those values into the sdk, but technically this could be skipped), FCL then takes that data, talks to the wallets, gets back signatures, then sends that data back to the backend, where the backend pulls that data apart and submits it to the chain via the GO-SDK. Admittedly this is currently clunky, but over time we should see these align more and more.

We can conceptually take this a bit further too. The following concept will be hard to make work nicely with something like ledger, especially in the beginning, but the way Blocto on FCL and the way Dapper will work with FCL, they create back channels between dapp and the wallet. It is entirely feasible that you can sign a transaction remotely from your phone with out ever seeing an iframe or pop up in the dapp. The architecture and implementation already allows for this. Those backchannels happen via routing that FCL currently gets during authentication, but it is entirely possible that this routing could be discovered in other ways, and wallets that support this (I’ve been calling Remote Asynchronous Signing) could enable an entirely different world of applications to appear.

Imagine for instance you are in a shop, and they are offering a “Pay with Flow” option. They can initiate the transaction from their ipad, and once getting your Flow Address, the application can route the transaction to your phone for approval, your phone gets a push message, you can then approve or decline the transaction from your phone. Approving it signs the transaction using the Private Key that has never left the HSM in your phone.

We are still a ways away from this stuff, but I think we are trending towards it, and I also definitely think the path in that direction starts with the very same questions you are asking here.

3 Likes