-
Notifications
You must be signed in to change notification settings - Fork 118
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
ARC-0031 : Authentication with Algorand accounts #160
base: main
Are you sure you want to change the base?
Changes from 32 commits
8de6348
d33f1ad
34deaba
ecd70d7
5755c11
26e9e06
65bac9a
2a2e7f2
f5cdda1
e0cb732
7c705ed
d91fadc
3747928
886b0da
1f088af
1401c2d
ca7075e
4fff311
af20ddd
781a40e
ea66bb4
cb936e8
0da7597
1ce6371
5b9bbf6
151f0fc
ce6a80a
33b301e
e207398
f3583e9
4fb50de
143b735
d2bf119
6978019
13d861e
128b89f
c1a6c8d
51024c2
c6465fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,269 @@ | ||||||
--- | ||||||
arc: 31 | ||||||
title: Authentication with Algorand accounts | ||||||
description: Use Algorand accounts to authenticate with third-party services | ||||||
author: Stefano De Angelis (@deanstef) | ||||||
discussions-to: https://github.com/algorandfoundation/ARCs/issues/42 | ||||||
status: Draft | ||||||
type: Meta | ||||||
created: 2022-04-05 | ||||||
--- | ||||||
|
||||||
# Authentication with Algorand accounts | ||||||
|
||||||
A standard for authentication with Algorand accounts. | ||||||
|
||||||
## Abstract | ||||||
|
||||||
This ARC introduces a standard for authenticating users with their Algorand accounts. It leverages asymmetric encryption <*PK, SK*> to verify the identity of a user, owner of an Algorand account. This approach fosters the adoption of novel identity management systems for both Web3 and Web2 applications. | ||||||
|
||||||
### Definitions | ||||||
|
||||||
- **System**: any frontend/backend application, service provider, or in general an entity not based on blockchain; | ||||||
- **Credentials**: any type of authentication used by users to access their online accounts, e.g. username/password, PIN, public/secret key pair; | ||||||
- **Blockchain identity**: a public/secret key pair <*PK, SK*> representing a blockchain account; | ||||||
- **Algorand account**: a blockchain identity on Algorand identified with the key pair <*PKa, SKa*>; | ||||||
- **Algorand address**: the public key *PKa* of an Algorand account; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically the address is not exactly the public key as there is additional base32 encoding + checksum. I see the distinction is made clear later. |
||||||
- **User**: an Algorand account holder; | ||||||
- **Verifier**: a system that needs to verify the identity of a User; | ||||||
- **dApp**: a decentralized Algorand application that natively runs on the Algorand blockchain, aka "*smart contract*"; | ||||||
- **Wallet**: an off-chain application that stores the secret keys *SKa*s of Algorand accounts and can display and sign transactions for these accounts; | ||||||
- **message**: a generic string of bytes; | ||||||
- **digital signature**: a message signed with the private key of a blockchain identity. | ||||||
|
||||||
## Motivation | ||||||
|
||||||
In Web3 users interacting with dApps must be authenticated with a blockchain identity (account for Algorand). Having dApps and traditional Web2 systems increasingly more interconnected, it is not difficult to imagine users consuming services both from a dApp and a Web2 application simultaneously. In this case, a single source of authentication should be used to avoid separation between credentials used for dApps and traditional Web2 services. | ||||||
|
||||||
This ARC provides a standard for users' authentication in Web2 services leveraging Algorand accounts. | ||||||
|
||||||
## Specification | ||||||
|
||||||
The key words "**MUST**", "**MUST NOT**", "**REQUIRED**", "**SHALL**", "**SHALL NOT**", "**SHOULD**", "**SHOULD NOT**", "**RECOMMENDED**", "**MAY**", and "**OPTIONAL**" in this document are to be interpreted as described in <a href="https://www.ietf.org/rfc/rfc2119.txt">RFC-2119</a> | ||||||
> Comments like this are non-normative. | ||||||
|
||||||
Interfaces are defined in TypeScript. All the objects that are defined are valid JSON objects, and all JSON string types are UTF-8 encoded. | ||||||
|
||||||
This ARC uses interchangeably the terms "*blockchain address*", "*public key*", and "*PK*" to indicate the on-chain address of a blockchain identity, and in particular of an Algorand account. | ||||||
|
||||||
### Overview | ||||||
|
||||||
This document describes a standard approach to authenticate a User with a blockchain identity. Algorand addresses are used as a *unique identifiers*, and the secret keys to digitally sign *messages* as a proof of identity. | ||||||
|
||||||
To sum up, given an Algorand account <*PKa, SKa*>, this ARC defines a standards for: | ||||||
|
||||||
- creating an [ARC-31](./arc-0031.md) compliant digital signature with *SKa*; | ||||||
- verifying an [ARC-31](./arc-0031.md) compliant digital signature with *PKa*. | ||||||
|
||||||
### Assumptions | ||||||
|
||||||
The standard proposed in this document works under the following assumptions: | ||||||
|
||||||
- User and Verifier communicate over secure SSL/TLS encrypted channels; | ||||||
- The Verifier knows the Users’ *PKa*; | ||||||
- For each *PKa* the Verifier generates a unique message to be signed; | ||||||
- The message MUST change arbitrarily for each authentication request to avoid [replay attacks](https://en.wikipedia.org/wiki/Replay_attack); | ||||||
- User's secret key is safely kept into a Wallet; | ||||||
- Users do not change their public address *PKa* for authentication; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this explicitly forbid rekeying? How is rekeying hgandled? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe it would be better to reconsider this. IMO, the protocol (and the library) should also handle rekeyed accounts. dApp developers should not need to deal with handling this situation. I know that this means dropping the offline support, but when thinking about the main use cases of this feature, I think this is a must. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leftover from the previous ARC-13. This ARC now allows rekey |
||||||
- Users **MUST** use Algorand compliant keys to sign the messages; | ||||||
- LogicSigs and Application addresses are not supported; | ||||||
|
||||||
### Authentication Mechanism | ||||||
|
||||||
The authentication mechanism defined in this ARC works as follows: a Users sends an authentication request to the Verifier specifying the Algorand account <*PKa, SKa*>. | ||||||
|
||||||
> Note that Algorand transforms traditional 32-bytes cryptographic keys into more readable and user-friendly objects. A detailed description of such a transformation can be found on the <a href="https://developer.algorand.org/docs/get-details/accounts/#keys-and-addresses">developer portal</a>. | ||||||
|
||||||
The Verifier responds with a message to be signed with the account's secret key *SKa*. The User queries the Wallet to sign the message. At that stage, the Wallet **MUST** check the message origin with the expected Verifier (to protect Users from [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack)). Once the message is signed, the User sends the result back to the Verifier. Finally, the Verifier checks the signature and, if it is all good, authenticates the User. | ||||||
|
||||||
```mermaid | ||||||
sequenceDiagram | ||||||
actor A as Alice | ||||||
participant W as Wallet | ||||||
actor B as Bob | ||||||
A-->>W: Connect to Bob with PKa | ||||||
activate W | ||||||
W->>B: GET message for PKa | ||||||
activate B | ||||||
B->>B: Create a new msg for PKa | ||||||
B->>W: msg | ||||||
deactivate B | ||||||
W->>W: Check <msg, Bob> | ||||||
W-->>A: Show msg origin | ||||||
Note right of A: Confirm signature | ||||||
A-->>W: | ||||||
W->>B: <PKa, Sig(msg)> | ||||||
deactivate W | ||||||
activate B | ||||||
B->>B: Verify Sig(msg) with PKa and msg | ||||||
Note right of B: if Sig(msg) is valid<br/>then authenticate user | ||||||
B-->>A: Authentication OK/KO | ||||||
deactivate B | ||||||
``` | ||||||
|
||||||
The diagram above summarizes the proposed mechanism. We consider the User, **Alice**, owner of an Algorand account <*PKa, SKa*> of which the secret key *SKa* is stored into a **Wallet**. | ||||||
|
||||||
> A wallet is any type of Algorand wallet, such as hot wallets like <a href="https://www.purestake.com/technology/algosigner/">AlgoSigner</a>, <a href="https://wallet.myalgo.com/">MyAlgo Wallet</a> for browser and mobile wallets used through <a href="https://developer.algorand.org/docs/get-details/walletconnect/">WalletConnect</a>, and cold wallets like the <a href="https://www.ledger.com">Ledger Nano</a>. | ||||||
|
||||||
Alice authenticates herself to the Verifier **Bob** sending back the digital signature `Sig(msg)` of message `msg` provided by Bob. The mechanism proceeds as follows: | ||||||
|
||||||
1. Alice initiates an authentication with *PKa* to Bob; | ||||||
2. Bob generates a message `msg` to be signed by Alice's *SKa*; | ||||||
3. Alice signs `msg` using her wallet; the Wallet inspects and displays the `msg` origin to be sure it came from Bob; | ||||||
4. Alice sends back the tuple `<PKa, Sig(msg)>` to Bob; | ||||||
5. Bob verifies `Sig(msg)` with Alice's *PKa* and `msg`; | ||||||
6. If the signature is valid, Bob authenticates Alice. | ||||||
|
||||||
The ARC-31 defines a standardized message for authentication, called *Authentication Message*. | ||||||
|
||||||
### Authentication Message | ||||||
|
||||||
An *Authentication Message* `auth-msg` is a sequence of bytes representing a message to be signed. The Verifier requests Users to sign an `auth-msg` with their secret keys. Such a message **MUST** include the following information: | ||||||
|
||||||
- `domain name`: name of the Verifier domain; | ||||||
- `Algorand address`: User's *PKa* to be authenticated; | ||||||
- `nonce`: a unique/random value generated by the Verifier; | ||||||
- `description`: Verifier details or general description; | ||||||
- `metadata`: arbitrary message data. | ||||||
|
||||||
The *Authentication Message* is represented with the following interface: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any length constraints? |
||||||
|
||||||
```typescript | ||||||
interface AuthMessage { | ||||||
/** The domain name of the Verifier */ | ||||||
domain: string; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are the rules for the domain? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should do exact protocol + domain + port There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @0x9090 as specified in the assumptions, the protocol MUST be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Careful - https is just the scheme. You can still connect via https on other ports. Requiring TLS is one thing via HTTPS:[...] but the port is independent and shouldn't be prevented from being part of the URI. 8443 is quite common as well as there being arbitrary ports that might be used in development, testing, etc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for clarifying, Patrick. What I meant is that in the most cases port will be 443 (it is the most common port for web browsing data transmission). Certainly, other ports can be used over HTTPS, and I agree that we should not prevent them from being part of the URI. As long as we require secure communication channels, we should be good. |
||||||
/** Algorand account to authenticate with*/ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
authAcc: string; | ||||||
/** Unique random nonce generated by the Verifier */ | ||||||
nonce: string; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
/** Optional, description of the Verifier */ | ||||||
desc?: string; | ||||||
/** Optional, metadata */ | ||||||
meta?: string; | ||||||
} | ||||||
``` | ||||||
|
||||||
The `auth-msg` **SHOULD** be compliant with [ARC-2](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0002.md), having the parameter `\<dapp-name\>`=`arc31`, for example: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is needed. ARC-2 is for notes in transactions. |
||||||
|
||||||
```json | ||||||
arc31:j{ | ||||||
"domain": "www.verifierdomain.com", | ||||||
"authAcc": "KTGP47G64KCXWJS64W7SGJNKTHE37TYDCI64USXI3XOYE6ZSH4LCI7NIDA", | ||||||
"nonce": "1234abcde!%", | ||||||
"desc": "The Verifier", | ||||||
"meta": "arbitrary attached data", | ||||||
} | ||||||
``` | ||||||
|
||||||
The `nonce` field **MUST** be unique for each authentication and **MUST NOT** be used more than once to avoid replay attacks. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minimum recommended length? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 15 characters minimum, which must include upper case and lower case letters and numbers. Nonce checking must be case sensitive. Nonce must be derived from a cryptographically strong PRNG. This would give a search space of 7.82 x 10^26 making it impossible to guess |
||||||
|
||||||
#### Signing the Authentication Message | ||||||
|
||||||
The `auth-msg` **MUST** be exchanged as a base64 encoded [msgpacked](https://msgpack.org/index.html) message, prefixed with the `"AX"` domain separator, such that: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Above the authentication message is presented as a JSON object. |
||||||
|
||||||
`msg =("AX" + msgpack_encode(auth-msg))`. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would recommend using prefix |
||||||
|
||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An issue with the above is that currently Ledger would not support signing such messages. This would require an update of Ledger. |
||||||
### Authenticate Rekeyed Accounts | ||||||
|
||||||
Algorand accounts can be rekeyed. Rekeying means that the signing key of a static public address *PKa* is dynamically rotated to another secret key. In this case, the original address controlled by *SKa'* is called *authorization address* of *PKa*, and it **MUST** be used to check the signature of *PKa*. In this ARC we indicate the *authorization address* with the account *<PKa', SKa'>*. | ||||||
|
||||||
> To learn more about Algorand Rekeying feature visit the [Rekey section](https://developer.algorand.org/docs/get-details/accounts/rekey/?from_query=rekey#create-publication-overlay) of the developer portal. | ||||||
|
||||||
The *authorization address* of an account can be checked directly from the Algorand blockchain. Indeed, a Verifier can inspect the [account API](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2accountsaddress) to check the account's `auth-addr` parameter. This parameter, if not empty, indicates the *authorization address* *PKa'*. | ||||||
|
||||||
The ARC-31 allows rekeyed accounts to be used for authentication. In this case, the Verifier must check the signature of a *PKa* with the *authorization address* *PKa'*. This address must be provided by the User along with the original address *PKa* and the digital signature. The Verifier, on his side, can check the validity of *PKa'* by looking at the Algorand blockchain. The diagram below details the protocol handling rekeyed accounts. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this mean that the verifier MUST always check if an account is rekeyed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I would say IIUC the ARC is defining a way to authenticate with Algorand Account. Now, an Algorand Account is an entity that has a meaning iff I specify both an address and the In particular the following steps of the protocol:
Can not work unless the verifier knows |
||||||
|
||||||
```mermaid | ||||||
sequenceDiagram | ||||||
actor A as Alice | ||||||
participant W as Wallet | ||||||
actor B as Bob | ||||||
participant Algorand | ||||||
A-->>W: Connect to Bob with PKa | ||||||
activate W | ||||||
W->>B: GET message for PKa | ||||||
activate B | ||||||
B->>B: Create a new msg for PKa | ||||||
B->>W: msg | ||||||
deactivate B | ||||||
W->>W: Check <msg, Bob> | ||||||
W-->>A: Show msg origin | ||||||
Note right of A: Confirm signature with <br/>auth-addr <PKa', SKa'> | ||||||
A-->>W: | ||||||
W->>B: <PKa, PKa', Sig(msg)> | ||||||
deactivate W | ||||||
activate B | ||||||
B->>Algorand: Retrieve PKa auth-addr | ||||||
activate Algorand | ||||||
Algorand-->>B: PKa' | ||||||
deactivate Algorand | ||||||
B->>B: Verify PKa auth-addr | ||||||
B->>B: Verify Sig(msg) with PKa' and msg | ||||||
Note right of B: if Sig(msg) is valid<br/>then authenticate user | ||||||
B-->>A: Authentication OK/KO | ||||||
deactivate B | ||||||
``` | ||||||
|
||||||
### Authenticate Multisignature Accounts | ||||||
|
||||||
Algorand accounts can be Multisignature (or MultiSig). A MultiSig account is a logical representation of an ordered set of addresses with a threshold and version. | ||||||
|
||||||
> To learn more about Algorand MultiSig feature visit the [Multisignature section](https://developer.algorand.org/docs/get-details/accounts/create/#multisignature) of the developer portal. | ||||||
|
||||||
The ARC-31 allows MultiSig accounts to be used for authentication. Assuming a MultiSig address *PKa_msig* composed by three Algorand accounts identified with the addresses *PKa', PKa'', PKa'''*, `threshold=2`, and `version=1`, the authentication should work as follows: | ||||||
|
||||||
1. An authentication request with *PKa_msig* is sent from the User to the Verifier; | ||||||
2. The Verifier responds with a new authentication message `msg`; | ||||||
3. The User collects the threshold signatures of `msg` and responds with *<PKa_msig, PKa', PKa'', PKa''', Sig'(msg), Sig''(msg), 2, 1>*, where *PKa*s are the ordered set of addresses of *PKa_msig*, *Sig*s are the collected signatures, `2` is the threshold and `1` is the MultiSig version; | ||||||
4. The Verifier firstly checks the *PKa_msig* against the list of addresses, the threshold and the version received, then verifies the signatures; if a threshold of valid signatures is received, the User can be authenticated. | ||||||
|
||||||
The diagram below details the protocol handling MultiSig accounts. | ||||||
|
||||||
```mermaid | ||||||
sequenceDiagram | ||||||
actor A as Alice | ||||||
participant W as Wallet | ||||||
actor B as Bob | ||||||
A-->>W: Connect to Bob with PKa_msig | ||||||
activate W | ||||||
W->>B: GET message for PKa_msig | ||||||
activate B | ||||||
B->>B: Create a new msg for PKa_msig | ||||||
B->>W: msg | ||||||
deactivate B | ||||||
W->>W: Check <msg, Bob> | ||||||
W-->>A: Show msg origin | ||||||
Note right of A: Collect signatures with <br/>PKa' and PKa'' | ||||||
A-->>W: | ||||||
W->>B: <PKa_msig, PKa', PKa'', PKa''', Sig'(msg), Sig''(msg), 2, 1> | ||||||
deactivate W | ||||||
activate B | ||||||
B->>B: Verify PKa_msig with (PKa', PKa'', PKa''', 2, 1) | ||||||
B->>B: Verify Sig'(msg) with PKa' and msg | ||||||
B->>B: Verify Sig''(msg) with PKa'' and msg | ||||||
Note right of B: if threshold of valid signatures<br/>then authenticate user | ||||||
B-->>A: Authentication OK/KO | ||||||
deactivate B | ||||||
``` | ||||||
|
||||||
### How to verify a digital signature? | ||||||
|
||||||
A digital signature generated with the secret key *SKa* of an Algorand account can be verified with its respective 32-byte public key *PKa*. The Verifier needs to decode the public key *PK* from the Algorand address, and it must know the original `auth-msg`. For example, assuming the digital signature `Sig(msg)` the Verifier can validate it using the Algorand SDK as follows: | ||||||
|
||||||
1. decode the Algorand address into a traditional 32-bytes public key *PK*; | ||||||
2. Compute `msg =("AX" + msgpack_encode(auth-msg))`; | ||||||
3. use an open-source cryptographic library (e.g. Python lib PyNaCl) to verify the signature `Sig(msg)` with *PK*. | ||||||
|
||||||
## Security Considerations | ||||||
|
||||||
An attacker **MAY** attempt to cheat with the system by impersonating another User or Verifier. This is possible if the attacker can intercept the digital signature and use the same signature in a replay-attack or man-in-the-middle attack. To mitigate this scenario, the Verifier **MUST** generate a new message for each authentication request, and Wallets must always check the `auth-msg` domain field. | ||||||
|
||||||
## Reference Implementation | ||||||
|
||||||
The ARC-31 reference implementation is available in the `assets` directory of this repo `assets/arc-0031`. It provides an example of client-server authentication with ARC-31. The reference implementation uses [MyAlgoWallet](https://wallet.myalgo.com/) as the unique wallet (at the time of writing) providing the possibility of signing random bytes. | ||||||
|
||||||
Reference implementation credits: [mrcointreau](https://github.com/mrcointreau) | ||||||
|
||||||
## Copyright | ||||||
|
||||||
Copyright and related rights waived via <a href="https://creativecommons.org/publicdomain/zero/1.0/">CCO</a>. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
pnpm-debug.log* | ||
lerna-debug.log* | ||
|
||
node_modules | ||
.DS_Store | ||
dist | ||
dist-ssr | ||
coverage | ||
*.local | ||
.env | ||
.env.* | ||
|
||
/cypress/videos/ | ||
/cypress/screenshots/ | ||
|
||
# Editor directories and files | ||
.vscode/* | ||
!.vscode/extensions.json | ||
.idea | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln | ||
*.sw? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
pnpm-debug.log* | ||
lerna-debug.log* | ||
|
||
node_modules | ||
.DS_Store | ||
dist | ||
dist-ssr | ||
coverage | ||
*.local | ||
.env | ||
.env.*. | ||
!.env.example | ||
|
||
# Nuxt dev/build outputs | ||
.output | ||
.nuxt | ||
|
||
/cypress/videos/ | ||
/cypress/screenshots/ | ||
|
||
# Editor directories and files | ||
.vscode/* | ||
!.vscode/extensions.json | ||
!.vscode/settings.json.example | ||
.idea | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln | ||
*.sw? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"printWidth": 120, | ||
"tabWidth": 2, | ||
"useTabs": false, | ||
"semi": false, | ||
"singleQuote": true, | ||
"trailingComma": "none", | ||
"bracketSpacing": true, | ||
"arrowParens": "avoid", | ||
"htmlWhitespaceSensitivity": "strict" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"editor.formatOnSave": true, | ||
"editor.defaultFormatter": "esbenp.prettier-vscode", | ||
"eslint.workingDirectories": ["client", "server"], | ||
"prettier.requireConfig": true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe specify here that multisig are supported too.