Skip to content
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

NEP 0413: Add signMessage method to Wallet Standard #413

Merged
merged 44 commits into from
Feb 24, 2023
Merged
Changes from 13 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1dbc444
feat: add `verifyOwner` to Bridger Wallets Standards
gutsyphilip Oct 12, 2022
4327348
NEP Draft
gutsyphilip Oct 25, 2022
125ab00
cleanup
gutsyphilip Oct 25, 2022
6567fb5
Merge branch 'near:master' into nep/wallet-werifyOwner
gutsyphilip Oct 26, 2022
e4ece58
cleanup
gutsyphilip Oct 26, 2022
8cc4e6e
Update BridgeWallets.md
gutsyphilip Oct 26, 2022
763d0ae
chore: some PR review cleanups
gutsyphilip Oct 31, 2022
287153d
Merge branch 'nep/wallet-werifyOwner' of github.com:gutsyphilip/NEPs …
gutsyphilip Oct 31, 2022
6a14033
fix: update spec
gutsyphilip Nov 1, 2022
af93733
Update nep-0413.md
gagdiez Nov 11, 2022
eff4cc9
Better explained base64 parameters
gagdiez Nov 11, 2022
c2512a4
Public key format is now "<type>:<bytes>"
gagdiez Nov 14, 2022
9327ae8
Update nep-0413.md
gagdiez Nov 15, 2022
34fed8c
Tested references & minor change to return types
gagdiez Nov 15, 2022
ab4b493
Simplified NEP + Added `domain` input
gagdiez Nov 16, 2022
cba446e
Added hash domain
gagdiez Nov 18, 2022
23eeda8
Removed accountId from Payload
gagdiez Nov 18, 2022
343fe16
Changed message for nonce
gagdiez Nov 18, 2022
dbaeece
Reverted nonce change to bring back message
gagdiez Nov 18, 2022
b8d698f
Changed message for a domain+nonce challenge
gagdiez Nov 18, 2022
0084811
Added colons to the hash namespace
DavidM-D Nov 20, 2022
129b452
Update nep-0413.md
gagdiez Nov 21, 2022
36b02bc
Update nep-0413.md
gagdiez Nov 22, 2022
5b9ddc0
Renamed to signMessage
gagdiez Nov 29, 2022
1115399
Renamed output structure
gagdiez Nov 29, 2022
2e13c7c
Explained nonce's type
gagdiez Dec 1, 2022
9159dec
Corrected nonce type (`number[]` -> `Buffer`)
gagdiez Dec 2, 2022
dd84fb4
Fixed wrong naming
gagdiez Dec 19, 2022
a18917a
Added Ledger drawback
gagdiez Dec 19, 2022
8721a7a
Removed wrong quoting
gagdiez Dec 21, 2022
16eeef1
Ordered attributes in example
gagdiez Dec 22, 2022
112dd78
Changed receiver for recipient
gagdiez Jan 20, 2023
a84a43f
Fixed wrong encoding for returning keys
gagdiez Jan 20, 2023
c4065d2
Added Decision Context
gagdiez Jan 27, 2023
be0e30d
Merge branch 'master' into nep/wallet-werifyOwner
gagdiez Jan 30, 2023
d278e48
Fixed minor typo
gagdiez Feb 1, 2023
17c4ec5
Update nep-0413.md
gagdiez Feb 11, 2023
d68a682
More explicit statement on fragments
gagdiez Feb 11, 2023
2e43d0e
State variable in message for CSRF mitigation
gagdiez Feb 16, 2023
5d5712a
fixed typo
gagdiez Feb 16, 2023
23bd3ac
Use implicit prefix following NEP461 update
gagdiez Feb 17, 2023
3a33754
Text improvement
gagdiez Feb 23, 2023
53070e7
Implemented optional state parameter
gagdiez Feb 23, 2023
8b0b05c
Merge branch 'master' into nep/wallet-werifyOwner
gagdiez Feb 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions neps/nep-0413.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
---
NEP: 413
Title: Near Wallet API - support for verifyOwner method
Author: Philip Obosi <[email protected]>, Guillermo Gallardo <[email protected]>
# DiscussionsTo:
Status: Draft
Type: Standards Track
Category: Wallet
Created: 25-Oct-2022
---

## Summary

A standardized Wallet API method, namely `verifyOwner`, that allows users to be authenticated in third-party services using their NEAR account.

## Motivation
NEAR users want to use their accounts as means of authentication in third-party services, in a similar fashion to how mainstream Web2 accounts can be used (e.g. "Login with Facebook").

Currently, there is no standardized way for wallets to sign a message for authentication.

## Rationale and Alternatives
Users want to be authenticated into a service without incurring in GAS fees, nor compromising their account's security. This means that:

1) The message must be signed off chain, with no transaction being involved.
2) The message being signed must include the service's name.
3) The message being signed cannot represent a valid transaction.
3) The message must be signed using a Full Access Key.
4) The signature should be simple to produce/verify, and transmitted securely.

### Why Off Chain?
So the user would not incur in GAS fees, nor the signed authentication gets broadcasted into a public network.

### Why The Message Being Signed MUST NOT be a Transaction?
An attacker could make the user inadvertently sign a valid transaction which, once signed, could be submitted into the network to execute it.

### Why The Message Needs to Include a Service Identifier?
To avoid replay attacks, i.e. a malicious app requesting the user to authenticate in their service, only to use the authentication message in a third-app.

Including the service's identifier (e.g. domain name) and making sure the user knows about it should mitigate this kind of attacks.

### Why a FullAccess Key? Why Not Simply Creating an [FunctionCall Key](https://docs.near.org/concepts/basics/accounts/access-keys) for Signing?
The most common flow for [NEAR user authentication into a Web3 frontend](https://docs.near.org/develop/integrate/frontend#user-sign-in--sign-out) involves the creation of a [FunctionCall Key](](https://docs.near.org/concepts/basics/accounts/access-keys)).

One might feel tempted to reproduce such process here, for example, by creating a key that can only be used to call a non-existing method in the user's account. This is a bad idea for several reasons:
1. It implies that the user needs to expend gas in creating a new key.
2. Since any third-party can ask the user to create a `FunctionCall Key`, this opens a vector of attack.

Using a FullAccess key allows us to be sure that the message was signed by the user (since nobody should have access to their `FullAccess Key`), while keeping the constraints of not expending gas in the process (because no new key needs to be created).

### How to Return the Signed Message in a Safe Way
Sending the signature in a query string to an arbitrary URL (even within the correct domain) is not secure as the data can be leaked (e.g. through headers, etc). Using URL fragments instead will improve security, since the fragment isn't included in the Referrer. Further constraining the callback URL to a fixed value will improve security even more.

### NEAR Signatures
NEAR transaction signatures are not plain Ed25519 signatures but Ed25519 signatures of a SHA-256 hash (see [near/nearcore#2835](https://github.com/near/nearcore/issues/2835)). Any protocol that signs anything with NEAR account keys should use the same signature format.

## Specification
Given the previous [rationales](#rationale-and-alternatives), this NEP proposes the wallets to implement a `verifyOwner` method following the specifications bellow.


### Goal
The `verifyOwner` method must take a plain text `message` and transform it into a signature.

### Input Interface
In order to sign a message, `verifyOwner` must implement the following input interface:

```jsx
interface VerifyOwnerParams {
message: string; // The message requested to be signed, should contain the app name.
callbackUrl?: string; // Applicable to browser wallets (e.g. MyNearWallet). This is the callback url once the signing is approved. Defaults to `window.location.href`.
gagdiez marked this conversation as resolved.
Show resolved Hide resolved
meta?: string; // Applicable to browser wallets (e.g. MyNearWallet) extra data that will be passed to the callback url once the signing is approved.
}
```

### Structure and Signature
In order to respect all the points layed in [rationales](#rationale-and-alternatives), `verifyOwner` must:

1. Embed the passed `message` into a predefined structure (see below).
2. Stringify the structure
2. Compute the `SHA256 hash` of the *stringified structure*.
3. Use a full-access key to sign the resulting `SHA256 hash`.

#### Structure
The structure must have the following interface:

```rust
struct Payload {
accountId: string; // Mandatory: the account name as plain text (e.g. "alice.near")
message: string; // The same message passed in `VerifyOwnerParams.message`
gagdiez marked this conversation as resolved.
Show resolved Hide resolved
blockId: string; // The hash of a block created close to the time of signature
publicKey: string; // public counterpart of the key used to sign, encoded as a string with format "<key-type>:<base-64-key-bytes>"
}
```

#### Signature
In order to create a signature, the `Payload` must be converted into its `string` representation (respecting the attribute's order given above), and then hashed using `SHA256`.

For example, assuming that:
1. `alice.near` wants to sign the message `"berryclub.io"`.
2. The wallet stores a private key which public counterpart is: `ed25519:6TupyNrcHGTt5XRLmHTc2KGaiSbjhQi1KHtCXTgbcr4Y`
3. A recent block was produced with the block hash `12FaoFyjYGmuxXnvWGYqxvUqGesSjdFVBNWXkXvoj41M`.

The wallet should sign the **`SHA256` hash** of the following string:

```jsx
sha256.hash(`{"accountId":"alice.near","message":"berryclub.io","blockId":"12FaoFyjYGmuxXnvWGYqxvUqGesSjdFVBNWXkXvoj41M","publicKey":"ed25519:6TupyNrcHGTt5XRLmHTc2KGaiSbjhQi1KHtCXTgbcr4Y"}`)
gagdiez marked this conversation as resolved.
Show resolved Hide resolved
```

### Output Interface
`verifyOwner` must return a object containing the **base64** representation of the `signature`, and all the data necessary to verify such signature.

```jsx
interface AuthenticationToken {
accountId: string; // The account name as plain text (e.g. "alice.near")
message: string; // The same message passed in `VerifyOwnerParams.message`
blockId: string; // The hash of a block created close to the time of signature
publicKey: string; // The public counterpart of the key used to sign, expressed as a string with format "<key-type>:<base-64-key-bytes>"
gagdiez marked this conversation as resolved.
Show resolved Hide resolved
signature: string; // The base64 representation of the signature.
gagdiez marked this conversation as resolved.
Show resolved Hide resolved
}
```

### Returning the signature
Web Wallets, such as [MyNearWallet](https://mynearwallet.com), should directly return the `AuthenticationToken` to the `VerifyOwnerParams.callbackUrl`, passing `accountId`, `message`, `blockId`, `publicKey` as strings, and the signature as an URL fragment. This is: `<callbackUrl>?accountId=<accountId>&message=<message>...#signature=<signature>`.

Non-web Wallets, such as [Ledger](https://www.ledger.com), can directly return the `AuthenticationToken`, in preference as a JSON object.

### Verifying the Signature
To authenticate a user via the `AuthenticationToken` there are 5 steps that you need to perform:
1. Make sure the `AuthenticationToken.message` is exactly the one you asked the user to sign.
2. Verify that the `AuthenticationToken.publicKey` belongs to the `AuthenticationToken.accountId`.
3. Reconstruct the `Payload` locally
4. Use the `AuthenticationToken.publicKey` to verify that the reconstructed `Payload` matches the `AuthenticationToken.signature`.
5. (Optionally) Make sure that the signature is recent by using the `timestamp` of the block with hash `AuthenticationToken.blockId`.
gagdiez marked this conversation as resolved.
Show resolved Hide resolved

> See the [reference section](#references---verification) for an example code on how to verify a user.

## References

### Signature
This interface is now supported by Wallet Selector as shown below:

1. [Pull Request Implementing `verifyOwner` in near/wallet-selector#391](https://github.com/near/wallet-selector/pull/391).
2. [https://github.com/near/wallet-selector/pull/436/files](https://github.com/near/wallet-selector/pull/436/files).

### Verification
We also include an example on [how to verify the authentication signature](https://github.com/gagdiez/near-login/blob/main/server/authenticate/wallet-authenticate.js).

## Drawbacks
gagdiez marked this conversation as resolved.
Show resolved Hide resolved
Accounts that do not hold a FullAccess Key will not be able to sign authentication messages, however, this is a necessary tradeoff for security.

## Copyright
[copyright]: #copyright

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).