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 39 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
173 changes: 173 additions & 0 deletions neps/nep-0413.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
---
NEP: 413
Title: Near Wallet API - support for signMessage method
Author: Philip Obosi <[email protected]>, Guillermo Gallardo <[email protected]>
# DiscussionsTo:
Status: Approved
Type: Standards Track
Category: Wallet
Created: 25-Oct-2022
---

## Summary

A standardized Wallet API method, namely `signMessage`, that allows users to sign a message for a specific recipient using their NEAR account.

## Motivation
NEAR users want to create messages destined to a specific recipient using their accounts. This has multiple applications, one of them being authentication in third-party services.

Currently, there is no standardized way for wallets to sign a message destined to a specific recipient.

## Rationale and Alternatives
Users want to sign messages for a specific recipient without incurring in GAS fees, nor compromising their account's security. This means that the message being signed:

1) Must be signed off-chain, with no transactions being involved.
2) Must include the recipient's name and a nonce.
3) Cannot represent a valid transaction.
3) Must be signed using a Full Access Key.
4) Should be simple to produce/verify, and transmitted securely.

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

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

#### How to Ensure the Message is not a Transaction
In NEAR, transactions are encoded in Borsh before being signed. The first attribute of a transaction is a `signerId: string`, which is encoded as: (1) 4 bytes representing the string's length, (2) N bytes representing the string itself.

By prepending the prefix tag $2^{31} + 413$ we can both ensure that (1) the whole message is an invalid transaction (since the string would be too long to parse), (2) this NEP is ready for a potential future protocol update, in which non-consensus messages are tagged using $2^{31}$ + NEP-number.
gagdiez marked this conversation as resolved.
Show resolved Hide resolved

### Why The Message Needs to Include a Receiver and Nonce?
To stop a malicious app from requesting the user to sign a message for them, only to relay it to a third-party. Including the recipient and making sure the user knows about it should mitigate these kind of attacks.

Meanwhile, including a nonce helps to mitigate replay attacks, in which an attacker can delay or re-send a signed message.

### Why using 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 because:
1. The user would need to expend gas in creating a new key.
2. Any third-party can ask the user to create a `FunctionCall Key`, thus opening an attack vector.

Using a FullAccess key allows us to be sure that the challenge 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 signed message 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 [URL fragments are not included in the `Referer`](https://greenbytes.de/tech/webdav/rfc2616.html#header.referer).

### 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
Wallets must implement a `signMessage` method, which takes a `message` destined to a specific `recipient` and transform it into a verifiable signature.

### Input Interface
`signMessage` must implement the following input interface:

```jsx
interface SignMessageParams {
message: string ; // The message that wants to be transmitted.
recipient: string; // The recipient to whom the message is destined (e.g. "alice.near" or "myapp.com").
nonce: [u8; 32] ; // A nonce that uniquely identifies this instance of the message, denoted as a 32 bytes array (a fixed `Buffer` in JS/TS).
callbackUrl?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). The URL to call after the signing process. Defaults to `window.location.href`.
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface is unclear and redundant. In general, there are two main reasons someone may want to sign a message: as a way of federated login (to prove wallet ownership) and to authenticate a human-readable message for another person. This NEP seems mostly focused on the first use case, for which there is no need for a message. Likewise, the second use case doesn't need all the other parameters.
Those different types of usage should preferably be separated, so that a signature made for one purpose could not be reinterpreted for the other. For example, this NEP may be reformulated to be only for login, in which case the message parameter can be removed.
Another problem here is the recipient parameter. Is it an account ID or a domain name? Verifying the recipient properly is very important for security, yet this NEP doesn't specify how this should be done. The most secure approach would be to encode the entire callback URL into the signed message, this should prevent any tampering.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This NEP seems mostly focused on the first use case, for which there is no need for a message.

Well, it started there, but the scope was decided to be expanded to allow more generic message signing. It could be a separate NEP (if necessary) that will be based on this NEP.

Likewise, the second use case doesn't need all the other parameters.

Can you elaborate on which ones and why they are not necessary?

Those different types of usage should preferably be separated, so that a signature made for one purpose could not be reinterpreted for the other.

That is a good point, but I believe it is outside of the scope of this NEP. Further protocols based on this NEP (e.g. proof of wallet ownership) should define their message structure and then Wallets would be able to display the requests properly, and thus keep their users informed about the type of request being signed.

Another problem here is the recipient parameter. Is it an account ID or a domain name? Verifying the recipient properly is very important for security, yet this NEP doesn't specify how this should be done.

@abacabadabacaba I believe this field is used to ensure that the Wallet was signing the message for this recipient alone, and is not used to authenticate the recipient for the Wallet. The recipient could be an account, a domain, or anything else, and it is the responsibility of the recipient of the signed message to validate that the message was not constructed for someone else and reused here. (@gagdiez @gutsyphilip I believe it is important to elaborate on the role of the recipient field in the NEP itself)

Copy link
Contributor

@gagdiez gagdiez Feb 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@abacabadabacaba The NEP is a result of 100+ messages in this discussion. The community wanted a way to sign a string message for a specific receiver (e.g. "anna.near", "myweb.com", or "aunt Mary"). Since some people wanted to use this as a way of login-while-passing-data (see the discussion), a nonce was added, so the same message for the same receiver could not be used twice.

While I understand the convenience of having two methods separated, this is not what the community was asking for. If there is no major security drawbacks, I would suggest to keep it like this.

If this does turn to be a major security drawback, we can collaborate on suggesting new changes, and maybe we can create another NEP together.

Copy link
Contributor

@esaminu esaminu Feb 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until we get more details on why recipient, nonce and callbackUrl are not needed for authenticating a human-readable message for another person the only action item I see here is to elaborate on the role of the recipient field perhaps providing some examples as well on the actual NEP.

I agree that it's preferable to have a more flexible standard that can be adapted to both use cases if there are no major security vulnerabilities that implementors of this standard are likely to face.

```

### Structure
`signMessage` must embed the input `message`, `recipient` and `nonce` into the following predefined structure:

```rust
struct Payload {
tag: u32 = 2147484061 // A fixed prefix tag (NEP461), always 2147484061
message: string; // The same message passed in `SignMessageParams.message`
nonce: [u8; 32]; // The same nonce passed in `SignMessageParams.nonce`
recipient: string; // The same recipient passed in `SignMessageParams.recipient`
callbackUrl?: string // The same callbackUrl passed in `SignMessageParams.message`
gagdiez marked this conversation as resolved.
Show resolved Hide resolved
}
```

### Signature
In order to create a signature, `signMessage` must:
1. Create a `Payload` object, making sure the `tag` is always 2147484061 (NEP461).
2. Convert the `payload` into its [Borsh Representation](https://borsh.io).
3. Compute the `SHA256` hash of the result from step 2.
4. Sign the resulting `SHA256` hash from step 3 using a **full-access** key.

> If the wallet does not hold any `full-access` keys, then it must return an error.

### Example
Assuming that the `signMessage` method was invoked, and that:
- The input `message` is `"hi"`
- The input `nonce` is `[0,...,31]`
- The input `recipient` is `"myapp.com"`
- The callbackUrl is `"myapp.com/callback"`
- The wallet stores a full-access private key

The wallet must construct and sign the following `SHA256` hash:

```jsx
sha256.hash(Borsh.serialize(Payload{tag: 2147484061, message:"hi", nonce:[0,...,31], recipient:"myapp.com", callbackUrl: "myapp.com/callback"}))
```

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

```jsx
interface SignedMessage {
gagdiez marked this conversation as resolved.
Show resolved Hide resolved
accountId: string; // The account name to which the publicKey corresponds as plain text (e.g. "alice.near")
publicKey: string; // The public counterpart of the key used to sign, expressed as a string with format "<key-type>:<base58-key-bytes>" (e.g. "ed25519:6TupyNrcHGTt5XRLmHTc2KGaiSbjhQi1KHtCXTgbcr4Y")
signature: string; // The base64 representation of the signature.
}
```

### Returning the signature
#### Web Wallets
Web Wallets, such as [MyNearWallet](https://mynearwallet.com), should directly return the `SignedMessage` to the `SignMessageParams.callbackUrl`, passing the `accountId`,`publicKey`, and the `signature` as URL fragments. This is: `<callbackUrl>#accountId=<accountId>&publicKey=<publicKey>&signature=<signature>`.

If the signing process fails, then the wallet must return an error message as a string parameter: `<callbackUrl>?error=<error-message-string>`.
gagdiez marked this conversation as resolved.
Show resolved Hide resolved

#### Other Wallets
Non-web Wallets, such as [Ledger](https://www.ledger.com) can directly return the `SignedMessage` (in preference as a JSON object) and raise an error on failure.

### Requesting the Signature for an Auth Protocol
While outside of the scope of this NEP, we feel the need to raise awareness on how to use this specification safely on an Authentication protocol. Because of this, we urge readers to learn about (CSRF attacks)[https://auth0.com/docs/secure/attack-protection/state-parameters] and implement ways to mitigate them, e.g. by keeping a `state` variable, which can be included in the `message` field, so it persists between the request of the signature and its authentication.

## References
A full example on how to implement the `signMessage` method can be [found here](https://github.com/gagdiez/near-login/blob/main/tests/authentication/auth.ava.ts#L27-#L65).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gagdiez is there any way/example on how to verify the signature for browser wallets?

The verifySignature function in the example needs the message, nonce, recipient and callbackUrl to re-construct the payload which was actually signed but we lose all this data on redirect?

What we get back in the URL is only the SignedMessage.

Copy link
Contributor

@gagdiez gagdiez Mar 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the current approach, you will need the user to pass it back to you. If you are implementing an app in which the user is asked to sign a message, then you will need to persist somewhere the parameters, redirect the user to the wallet, get the key+signature from fragment, and the rest of the parameters from the persisted storage.


## 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 this kind of messages. However, this is a necessary tradeoff for security since any third-party can ask the user to create a FunctionAccess key.

At the time of writing this NEP, the NEAR ledger app is unable to sign this kind of messages, since currently it can only sign pure transactions. This however can be overcomed by modifying the NEAR ledger app implementation in the near future.

Non-expert subjects could use this standard to authenticate users in an unsecure way. To anyone implementing an authentication service, we urge them to read about [CSRF attacks](https://auth0.com/docs/secure/attack-protection/state-parameters), and implement ways to mitigate, e.g. by keeping a local `state` variable between the request of the signature and its authentication, and include it in the `message` field.

## Decision Context

### 1.0.0 - Initial Version
The Wallet Standards Working Group members approved this NEP on January 17, 2023 ([meeting recording](https://youtu.be/Y6z7lUJSUuA)).

### 1.1.0 - First Revison
Important Security concerns were raised by a community member, driving us to change the proposed implementation.


### Benefits

- Makes it possible to authenticate users without having to add new access keys. This will improve UX, save money and will not increase the on-chain storage of the users' accounts.
- Makes it possible to authorize through jwt in web2 services using the NEAR account.
- Removes delays in adding transactions to the blockchain and makes the experience of using projects like NEAR Social better.

### Concerns

| # | Concern | Resolution | Status |
| - | - | - | - |
| 1 | Implementing the signMessage standard will divide wallets into those that will quickly add support for it and those that will take significantly longer. In this case, some services may not work correctly for some users | (1) Be careful when adding functionality with signMessage with legacy and ensure that alternative authorization methods are possible. For example by adding publicKey. (2) Oblige wallets to implement a standard in specific deadlines to save their support in wallet selector | Resolved |
| 2 | Large number of off-chain transactions will reduce activity in the blockchain and may negatively affect NEAR rate and attractiveness to third-party developers | There seems to be a general agreement that it is a good default | Resolved |
| 3 | `receiver` terminology can be misleading and confusing when existing functionality is taken into consideration (`signTransaction`) | It was recommended for the community to vote for a new name, and the NEP was updated changing `receiver` to `recipient` | Resolved |
| 4 | The NEP should emphasize that `nonce` and `receiver` should be clearly displayed to the user in the signing requests by wallets to achieve the desired security from these params being included | We strongly recommend the wallet to clearly display all the elements that compose the message being signed. However, this pertains to the wallet's UI and UX, and not to the method's specification, thus the NEP was not changed. | Resolved |
| 5 | NEP-408 (Injected Wallet API) should be extended with this new `signMessage` method | It is not a blocker for this NEP, but a follow-up NEP-extension proposal is welcome. | Resolved |

## Copyright
[copyright]: #copyright

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