-
Notifications
You must be signed in to change notification settings - Fork 84
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
base: main
Are you sure you want to change the base?
Conversation
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. |
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.
@friedger, thank you so much for doing this. I added a few comments, curious what you think :)
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. |
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.
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 |
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.
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.
|
||
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. |
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.
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?
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.
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.
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 | |
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.
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:
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.
+1 this seems like a good separation of concerns
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:
In the auth response section:
|
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
andassociationToken
->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 ofdid: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.