-
Notifications
You must be signed in to change notification settings - Fork 460
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Solana Pay message signing #152
Comments
Some thoughts --
/* req 1 */ { "account": "base58" }
/* res 1 */ { "account": "base58", "data": "base64", "mac": "base64" }
/* req 2 */ { "account": "base58", "data": "base64", "mac": "base64", "signature": "base64" }
/* res 2 */ 200 OK
|
Tagging @jnwng for feedback, he's been interested in this as well. |
|
Ah, what I meant is that /* req 1 */ { "account": "base58" }
/* res 1 */ { "data": "base64" }
/* req 2 */ { "account": "base58", "data": "base64", "signature": "base64" }
/* res 2 */ 200 OK
The server needs to be able to know that This suggests to me another field that's just passed back and forth as-is. It's intentionally opaque to the user and wallet. They don't need to see this value because they aren't signing it, and we don't care how long it is (within reason). For example,
I'm not sure neither, but it's an interesting constraint. We should try to understand what use cases there are for message signing besides "sign in". |
before i saw this issue, i was working on a proof-of-concept of a server-side verification flow that accommodates various efforts to standardize the semantics of "Sign-in with Solana". i was primarily focused on creating a reference example of a verifying the encoding of a signed transaction without submitting it to the network (workaround for Ledger), but wanted to bake in some of the in-flight thoughts here just to try them on for size.
definitely agree. the strategy outlined to handle the machine encryption seems suitable, although in my reference i skip it. i can give it a try and see how it feels! one pattern that i thought felt rather clean... instead of having a third hop to the server-side API, i baked in the message to sign in the this means that:
open to critique here! again, haven't really thought through the GET flow, although it made this client-side implementation fairly straightforward |
OTOH, this is also a lot of work to subvert the gas fee. in the short-term submitting a transaction with Memo using a nonce and validating with Solana Pay is a reasonably inexpensive process to execute, provided it is backed up on the server with a cookie / JWT that the client can use afterwards |
Got it. OAuth supports passing a plaintext state parameter that allows you to validate responses. Instead of
I did a 10 minutes of digging and authentication seems like the only active use-case so far. I'll put out some feelers and see if anyone is doing something more creative. From @jnwng's proof-of-concept:
This seems like a really good end goal for an OOB Solana auth solution, whether a user is using Solana Pay or a traditional wallet connection.
The Solana Pay dapps I've seen will usually pay these gas fees as requiring your users to pay to sign-in is not great UX. This opens the dapp up to attacks where a malicious actor repeatedly requests memos and drains the dapps fee paying account. Maybe not a huge concern in the short term. Offering real message signing based on this proposal also provides a more consistent dev experience. Here's a reference implementation of how we do Solana Pay authentication in Bedrock using memo transactions. This has only been used for toy apps so far but currently we don't pay fees for sign-in to avoid this exact issue. Live version here. |
While I agree, I view it as less about saving on fees and more about the UX benefits of enabling a lower trust threshold. Users must be more careful of signing a transaction than signing a message, so wallets have to handle the presentation differently. It's also private, being off-chain. You may not want to put something on chain to symbolize that your users were physically somewhere at some time doing something.
Yeah, this sounds about right. We should definitely learn as much from OAuth as we can here.
It may be worth getting feedback from some POAP and event/ticketing protocols (Cupcake, SOAP, and Cardinal come to mind). |
This is huge for live events. High profile consumers don't want to broadcast their location when scanning into an event.
I reached out to Cardinal, SOAP, Disco, Decaf, and Fantastix. Feedback incoming. |
Glad to see this being solved! There are a few ideas that this would make easier.
Without signatures, the only possible way rn is memo tx (which is very unoptimal) or the host scanning a qr on the guest's phone (qr as image of the ticket NFT) which brings almost no added benefit to NFT ticketing.
How this gets implemented on a technical level is out of my scope of expertise, but this allows for a lot of yet undiscovered use cases. |
glad to see this being discussed! Im from Monstrè @longeye_monstre, we built Solana Pay powered NFC Gift Cards for the hackathon. This is definitely not my area of expertise but keen to share some thoughts. Another use-case outside of events (but still falls under authentication) is wallet to wallet messaging. Dialect protocol would benefit greatly from this, imo. Will be dming you @samheutmaker on tg! |
Really cool to see this being worked on! One concern I have is do we want to specify whether/how apps can 'reject' a sign request? For example if I have an NFT-gated part of my app and somebody sends the POST request in this spec, I get their public key at that point. I could look up whether they have any of my NFTs, see they don't and send back a 403 instead of letting them authenticate with that key and then having my own in-app logic to check their access afterward. Is that a valid way for an app to behave? Or should a compliant app accept all signature requests with this scheme and only check privileges later? I think it's more obvious with browser wallets because AFAIK an app can't get in the middle of the connect flow and throw in their own checks, but this specification explicitly gives them the public key and ability to fail at that first (or second) hop. This probably applies to transaction requests too ATM, but there are probably more use cases for "you don't have access" than "you can't perform a transaction". Potentially related to #150 for both existing transaction requests and this? |
Yes, this is valid and expected. The spec doesn't specify the structure of error responses (yet) but it does specify that the wallet must handle 200 and 3xx-5xx responses: https://github.com/solana-labs/solana-pay/blob/master/SPEC.md#post-response |
Keep in mind that a user is not authenticated until they sign the message regardless of whether or not the address you received passes a token gate you may have set up. The user must sign the message to prove they hold the private key to that address. Given this, it might be helpful to think of authentication and authorization as two separate steps. It may be better to let them sign the message (authentication) and then block access if they fail to pass the token gate (authorization). The actual specification will leave it up to you to decide what is right for your dapp. |
This has been specified here: https://github.com/solana-labs/solana-pay/blob/master/message-signing-spec.md This is still alpha and unimplemented. If you're following this issue or have a use case for this, please review this spec! |
Edit: The information below is no longer up-to-date. A current version of the proposed changes can be viewed at #169.
All feedback is appreciated. I can be reached on telegram
@samhogan
.Original Issue
Solana Pay should support message signing via HTTP request. The following is an attempt to summarize how a dapp can request that a wallet sign a message via Solana Pay.
The flow is as follows:
The
<data>
field is the data that will be signed. It must be a base64-encoded value adhering to the Proposal for off-chain message signing solana#26915 specification once it is finalized.The
<state>
field is a MAC that the wallet will pass back to the server in order to verify that the contents of the<data>
field were not modified before signing.The
<message>
field is an optional UTF-8 string value to display to the user.The
<account>
field is the base-58 encoded value of the user's public keyThe
<state>
field is the same as the<state>
field from the first POST request response body. It should be returned to the server unmodified.The
<signature>
field is the base-58 encoded signature from signing the<data>
field with the users private key<state>
field to ensure that it has not changed and then attempt to verify the<signature>
against the content from the<state>
field and<account>
field. If the signature is verified, the server knows that the wallet controls the private key for the<account>
.The text was updated successfully, but these errors were encountered: