Standardizing proof of account ownership

I think a feature that will be broadly useful to business integration moving forward is the ability to prove ownership of a nano address (i.e. that you hold the private key). As an example use-case, my website (upst-art.net) currently has users send a small random amount after claiming ownership of an address. Upon receipt of that amount, we know they control the account and we credit all future deposits to the account and allow for convenient withdrawal to that account. I'm sure there are similar use-cases for a variety of businesses that might integrate nano. The network would benefit from being able to do this without using the public ledger.

The task should be fairly simple from the wallet side: just sign a message with your private key. But it would need to be a standardized feature across all wallets, so that a service implementing the proof requirement could expect every user to have the functionality available to them when asked. This is similar to my post on standardizing wallet transaction metadata.

I think it should be implemented as a nano: URI, similar to transactions, so that the user's operating system can automatically route the task to the appropriate software. It could then be scanned as a QR code or handled like any other nano interaction. The "challenge" would be presented like this:

nano:proof?challenge={random number}&url={optional web address to send the response to}

and the proof of ownership might be presented like this:

nano:proof?address={nano_123...xyz}&salt={random number}&response={the signature}

which is sent to the provided URL, or copied/paster into an input field. The challenge and salt are random numbers generated by each party for security (someone should check that this is safe from a cryptography standpoint) so that the proof is a one-time use for each party. Once received, the challenger knows that the responder holds the key.

I've seen a variety of quirky services that use the Nano wallet as a "login" tool. While this is not an unreasonable concept, it doesn't need to be on the ledger, and I think this would alleviate that use case.

As pointed out by u/mrten10 on Reddit, rather than a salt in the response, it would be better to have a timestamp in the challenge. Depending on the importance of the verification, you might set a smaller expiration window to accept a signature. An attacker would need to receive a challenge, forward that on to the real account holder within the timeframe, and get their signature within a small timeframe.

Secondly, instead of a random challenge, for some use-cases the challenge could be a fixed message such as "Log into servicename.com" that gets displayed on the confirmation screen of the wallet. You will never have a repeat signature since the time is always changing, and you can't just present the challenge for a different service without the user noticing. (Or the wallet noticing and warning the user.)

Regarding the format, as pointed out by u/Zergento7, I see that the nano: URI is specifically meant for payment, so we would want a different one like

nanosign:<challenge>?t=<timestamp>?to=<url>

and the response would be a JSON:

HTTP 1.1 POST <url>
...
{
  "address": "...",
  "signature": "..."
}

honestly I do not know enough about 25519 and signatures to say for sure if a predictable input can lower the security.

I think a salt wouldnt be bad. also just personally speaking it might also be great if the response could also keep the inputs (timestamp and challenge) so basically all can be verified easily as one package.

regarding repeat sigs, a hww at the very least would not really notice a change in time as HWWs usually dont have a concept of time

What's wrong with sending a small irrelevant sum to prove ownership? There are no fees on NANO, so I don't see any problems here.

It's additional traffic on the network that has no need to be on the network, and it's a better user experience. If you're thinking ahead towards mass adoption, this improves the throughput available for other uses. You don't create global protocols by ignoring easy opportunities for optimization. And users don't want to have to pay a small fee to do something if they don't have to.

Remember that "challenge" must include some prefix, otherwise you can forge blocks (and steal funds from the wallet).

So, given some ?challenge={random number}, you need to sign it as: "challenge_proof" || {random number}.

It initially seems to me that using JWTs would be a good choice for authentication purposes.

(1) It would be safe to take a JWT and simply sign it, as it wouldn't be a valid block, so a malicious signature requester wouldn't be able to steal funds from the authenticating user.

(2) JWTs, from the application's standpoint, are tamper proof, meaning that after the expiration date has passed, the payload wouldn't be predictable and replay attacks wouldn't be possible.

(3) JWTs can carry arbitrary payload, which would help the application that's requesting the authentication with carrying data over. Let's say the app already has you logged in as a user, by using the sub field of the JWT, it could know only from the request data which app user the signature/address ownership is connected to. Not only would the sub field be useful, but the app would be able to add as many fields as it sees helpful.

(4) Wallets could enforce that the JWT's iss (issuer) field shares the same origin as the server they should post the signature response to. This would bring the user the guarantee that they'll only "authenticate" with the website they expect to, not some unexpected random server.

One other thing to take into account is that what I propose above is specific to the web app authentication use-case and there may be others in which a JWT isn't as practical. So I also propose that we add a type parameter to the url and initially only support a value for it like jwt. This would allow for extensions to the signature standard in the future, while not requiring the initial spec to be more generic than it has to (for the use cases we seem to be wanting to solve).

Overall I think something like the following could work superbly.

  1. App server generates a JWT with the iss being its origin (or the origin of the authentication service)
  2. A URL with the following format is presented to the user (a hyperlink or QR code)
nanosign:<jwt>?type=jwt&to=<url-to-post-payload>
  1. The user either clicks the link or scans the QR code with a wallet that supports the standard
  2. (optional?) The wallet app validates that the to and iss share the same origin
  3. User confirms they really want to connect with such web app
  4. Wallet makes a post request to the url in the to parameter and the following payload
{
  "address": "...",
  "token": "<server jwt>",
  "signature": "..."
}
  1. server uses the infomation as it sees fit, wallet display success message

There are a couple of suggestions from the reddit thread that haven't found their way into what I proposed, the main one being the signature algorithm.

I'm not entirely sure on what is the best way we could go about adding that to what I proposed. Maybe it could be a search param in the URI, maybe a piece of information stored in the JWT itself. Maybe we could start by expecting signatures to be blake2b, or maybe using the same algorithm specified in the JWT header (the algorithm that was used by the server). Maybe we could also add the algorithm as a parameter to the post request done in step 6. I'm open to suggestions on that.

(Should I post this to the reddit thread as well?)

Taking a step back; this idea is better geared towards verifying user accounts, which could be a neat tool long term for Nano apps/systems. For payment flows however why not send the entire signed block if dealing with signatures already? A signed block handoff is also better for event driven processing and you can easily auto load app logic after posting the block. Thats the dream UX where user does nothing besides NFC tap / QR scan.

I think this is a great feature to add to an off chain protocol. The main feature set then being:

  • Signed Block handoff
  • Block hash handoff
  • signature / jwt handoff for user auth
  • flexible meta data handoff / storage?

Ideally its all written up in a client side library that wallet providers can use to easily implement. That was the motive behind the Nano bench idea.

I read through the Nano Bench thread (with which I wasn't familiar) and have a couple of questions (and points) regarding it + your comment.

I saw on another thread people talking about signed block handoff as an alternative to what we're discussing here, but judging from your comment (and what I understood from Nano Bench) I'm not sure whether you see it as an alternative to the authentication problem or just as a means to solve the payment integration problem and think the authentication-specific approach is valid by itself and could be part of what Nano bench proposes.

(Below this line are points regarding Nano bench which should probably be made in its thread, but since we're here...)

On the implementation of something close to what I gathered from the Nano bench thread, I think an approach that probably scales better and produces results faster for the community would be to have a repository with reference code for the "standards" we're trying to define that would be more legible rather then performant and have it built part by part rather than trying to go for a one-code-fits-all approach. I say that because wallets are implemented using different languages, frameworks, technologies and platforms depending on the use-case -- think of web (js) vs hybrid/mobile (js, dart) vs native mobile (swift, java), and even the differences between web js and node-like js (and poorly compatible React Native js). Also, the effort to properly implement a multi-faceted project such as what I understand Nano bench to be is really big and might take longer to yield results than if we just tried structuring development of standards "on their own" -- think of how GNU Hurd is still under development and does not yet have a stable build.

I think relieving application developers from having to build significant parts of wallet functionality and allowing them to focus on their apps instead would provide a significant improvement to Nano, as it is currently very hard to provide well integrated user experiences. Making the lives of wallet implementors easier is also something I can get behind, having had to implement what was basically a whole Nano wallet in order to add wallet sign-in into a project of mine. I'm not sure on the way to get there with a good balance between speed and quality, though :/.

I do like the above idea, and I think its cool it can be used for payment ids as well as user sign in. Its just the architecture required to implement it is so similar to block hand offs you might as well do it all.

And you are right at the end of the day you don't need an all-in-one library to do it. The most basic thing a merchant can do is provide a callback url with a variable describing what the endpoint expects. This can easily be embedded in the existing URI structure.

QR URI Ex: nano:nano_{account}?amount=1&handoff_type={hash/block/jwt/metadata}&handoff_url={merch api endpoint}

All we need is wallet providers to agree on adding couple lines of code for handling. Would be huge if Natrium or Nault lead by example.

There is one reason why I don't think the existing URI scheme works for authentication and it is because up until the user picks an account in the wallet app, the account is not known. This means it is not a good UX expectation for the web app to know the account address prior to generating the URI (or QR code). This is the reason why I think a different scheme is necessary. (This may also be true for check-outs as the app probably doesn't care to know the account the payment will come from until after the user may have chosen it.)

The different scheme being block/hash handoff? Yea I agree its better suited for payment ids.

Seriously though that above URI scheme is very flexible. If your app needs to track user accounts, you ask one time for a user nano account during sign up. After that you accept signed jwts via above URI scheme for user login. Need in app purchases? No prob, block or hash handoff have you covered.