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

SIP for Authentication Protocol #50

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open

Conversation

friedger
Copy link
Contributor

@friedger friedger commented Nov 19, 2021

This SIP defines a authentication protocol used by Stacks apps.

The current version has (hopefully) all the required information about the protocol as it is currently used.

I changed three properties of the auth response: hubUrl -> hub_url and associationToken -> association_token. profile.stxAddress-> stx_address.

I added state to the auth messages as defined in OAuth 2.0.

It is recommended to use did:stacks:v2 instead of did:btc-addr

For the public profile, this spec uses the Verifiable Credential model. The VC spec was chosen because it now has W3C Recommendation status.

@friedger friedger marked this pull request as ready for review January 10, 2022 14:17
@friedger
Copy link
Contributor Author

After the description of the current protocol 1.3.1, I have updated the spec to 2.0.0 in 7b35de6 using verifiable credentials and better definition of the issuers.

Copy link
Contributor

@aulneau aulneau left a comment

Choose a reason for hiding this comment

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

@friedger, thank you so much for doing this. I added a few comments, curious what you think :)

Comment on lines +42 to +45
1. Application creates app transit private key, signs an auth request with that key and sends the request to the Authenticator.
2. In the Authenticator, User authorizes sharing of public key, Authenticator derives app private key from request and updates the user's public profile if required.
3. Authenticator creates response with authorized data and sends response to the Application.
4. Application verifies signature against the app transit private key.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
1. Application creates app transit private key, signs an auth request with that key and sends the request to the Authenticator.
2. In the Authenticator, User authorizes sharing of public key, Authenticator derives app private key from request and updates the user's public profile if required.
3. Authenticator creates response with authorized data and sends response to the Application.
4. Application verifies signature against the app transit private key.
**Client (app):**
1. Client generates ephemeral private/public key pair: `transitPrivateKey`
2. Client creates an auth request payload (`authRequestPayload`) with details about the application, eg:
```ts
export interface AuthRequestPayload {
scopes: AuthScope[];
redirect_uri: string;
public_keys: string[];
domain_name: string;
appDetails: AppDetails;
}
```
3. Client signs the auth request payload (a JWT) with the `transitPrivateKey`, with signature format `ES256k`
4. Client sends the JWT to an authenticator (eg: Hiro Web Wallet)
**Authenticator (wallet):**
1. Authenticator validates the signature against the included public key(s)
2. Authenticator generates an account index by creating a SHA256 hash of the domain of the application that generated the `authRequestPayload`, and the wallet's salt
3. Authenticator then uses that index to derive a hardened child private key (`appPrivateKey`) from the root seed phrase (BIP32).
4. Authenticator generates an `authResponse` which includes the `appPrivateKey`
5. Authenticator encrypts the JWT with the public key provided in the `authRequestPayload`
**Client (app):**
1. Client receives the encrypted `authResponse` and decrypts it with the `transitPrivateKey`
2. Client is now authenticated

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to give a high level overview first. Ideally with a better diagram than the current one. I like the separation between the two entities, but I think it is too much detail. Maybe we should have this in pseudo code as an appendix? @aulneau

The auth response is not encrypted, only the appPrivateKey (and core_token). Messages are always normal JWTs.

sips/sip-x/sip-x-authentication-protocol.md Outdated Show resolved Hide resolved
sips/sip-x/sip-x-authentication-protocol.md Outdated Show resolved Hide resolved

1. Wallet salt: create the sha256 hash of the hex representation of the public key of derivation path (`m/888'/0'`)
1. Create sha256 hash of concatinated string of the `domain_name` from the request and the hex representation of the wallet salt.
1. Hash code: Create a hash code (as defined in Java and Javascript) from the hex representation of the hash, then apply bitwise `and` to the hash code and 0x7fffffff.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is highly prone to collisions, and generally a bad implementation. Unfortunately, it has been used for years. I have created what I think is a better alternative here in micro-stacks.

  // Rather than using the `hashCode` function to derive an index for the child
  // which makes it so there's a unreasonable high probability that someone can
  // log into a new app (website) and it accidentally shares data with another one
  //
  // we want to use the `appDomain` + `salt` to generate a sha256 hash that is
  // then used as the chaincode for the new app keychain
  const chainCode = hashSha256(utf8ToBytes(`${appDomain}${account.salt}`));
  const rootNode = HDKeychain.fromBase58(account.appsKey);
  const appKeychain = await HDKeychain.fromPrivateKey(rootNode.privateKey, chainCode).deriveChild(
    0,
    true
  );

With the formalization of this standard in a SIP, would it make sense to explore this (or another) alternative?

Copy link
Contributor

Choose a reason for hiding this comment

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

cc @zone117x @kantai for any thoughts you might have on this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting. We should fix that!

| public_keys | array | Single item array containing the public key of the selected account in hex representation. |
| profile | object | Object containing properties of the users, the object schema should be well-known type of Appendix B. This can be the public profile of the selected account. |
| apps_meta | object | Information about user data of different apps. Property names are the domain name of the app. Each property value is an object of containing properties for `storage` and `publicKey`. See "Application Meta Data". |
| username | string | BNS username, owned by the first public key of `public_keys` claim. Can be the empty string |
Copy link
Contributor

@janniks janniks Apr 19, 2022

Choose a reason for hiding this comment

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

We're currently in the process of removing the username from the authentication flow completely. hirosystems/stacks.js#1230 Keeping the username adds a bunch of complications. Additionally, the username should be verified by the application anyway, so it makes sense for the application to fetch the username in the first place.

I bumped the token version to 1.4.0 for this, in case anybody is validating very strictly.

some discussions/comments here:

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 this seems like a good separation of concerns

@jcnelson jcnelson added the Draft SIP is in draft status label Jun 9, 2022
@aulneau
Copy link
Contributor

aulneau commented Aug 7, 2022

I'm in the process of building a new library that implements this SIP in micro-stacks and I have a few questions:

In the auth request area:

manifest_uri: is this required anymore? I don't think we need it. Can you describe use cases for this?
redirect_uri: this is also not used anymore in the context of extension/native based wallets. this seems to be a hold over from early blockstack days.

In the auth response section:

core_token: is this needed? I don't think anything uses this, nor generates a token for it.
email wouldn't this be better in some profile? nothing uses this to date

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Draft SIP is in draft status
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants