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

ARC60 (new) - arb data signing AUTH, random, fido2, caip122 #313

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

Conversation

ehanoc
Copy link

@ehanoc ehanoc commented Sep 27, 2024

NEW ARC60 Proposal

This new proposal achieves the following:

  • For the purpose of AUTH (at least for now) sign truly arbitrary data without colliding with known data structures (TXs, LSIGs, etc)

  • Compatibility with several authentication mechanisms. i.e custom auth mechanisms, FIDO2 / Passkeys / CAIP-122

  • Takes into consideration the implementation constraints of hardware wallets

Reference Implementation is available

ehanoc and others added 2 commits September 27, 2024 11:37
@ehanoc ehanoc changed the title doc: New ARC60 arb data signing AUTH, random, fido2, caip122 ARC60 (new) - arb data signing AUTH, random, fido2, caip122 Sep 27, 2024
@ehanoc ehanoc closed this Sep 29, 2024
@ehanoc ehanoc reopened this Sep 29, 2024
Copy link
Contributor

@k13n k13n left a comment

Choose a reason for hiding this comment

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

Thanks @ehanoc. I really hope that this iteration of ARC-60 finally makes it past the finish line!

| `data` | `string` | string representing the content to be signed for the specific `Scope`. This can be an encoded JSON object or any other data. It **MUST** be presented to the user in a human-readable format. |
| `signer` | `bytes` | public key of the signer. This can the public related to an Algorand address or any other Ed25519 public key. |
| `domain` | `string` | This is the domain requesting the signature. It can be a URL, a DID, or any other identifier. It **MUST** be presented to the user to inform them about the context of the signature. |
| `requestId` | `string` | It is used to identify the request. It **MUST** be unique for each request. |
Copy link
Contributor

Choose a reason for hiding this comment

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

How can uniqueness be enforced? Would a wallet have to keep track of past requestIds? That sounds impractical

Copy link
Author

Choose a reason for hiding this comment

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

It's not in reality, but tracking uuids for instance is pretty straightforward.

| `signer` | `bytes` | public key of the signer. This can the public related to an Algorand address or any other Ed25519 public key. |
| `domain` | `string` | This is the domain requesting the signature. It can be a URL, a DID, or any other identifier. It **MUST** be presented to the user to inform them about the context of the signature. |
| `requestId` | `string` | It is used to identify the request. It **MUST** be unique for each request. |
| `authenticatedData` | `bytes` | It **MUST** include, at least, the `sha256` hash of the `domain` requesting a signture. The wallet **MUST** do an integrity check on the first 32 bytes of `authenticatedData` to match the hash. It **COULD** also include signature counters, network flags or any other unique data to prevent replay attacks or to trick user to sign unrelated data to the scope. The wallet **SHOULD** validate every field in `authenticatedData` before signing. Each `Scope` **MUST** specify if `authenticatedData` should be appended to the hash of the `data` before signing. |
Copy link
Contributor

Choose a reason for hiding this comment

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

The spec states that wallets SHOULD validate every field in authenticatedData. Given that authenticationData is basically a binary blob, how should a wallet know what to validate? Who defines the binary serialization format of authenticationData? Every individual scope?

Copy link
Author

Choose a reason for hiding this comment

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

Yes; can be scope specific.

The most important are the first 32 bytes that must be the SHA256(domain); also calculated by the wallet; ensures that the domain you are "talking" to is the same one you expect to be "connected" to and sending you data to sign.

All the remaining bytes, are context specific and depending on the scope validations could vary


Summarized signing process for `AUTH` scope:
```plaintext
EdDSA(SHA256(data) + SHA256(authenticatedData))
Copy link
Contributor

Choose a reason for hiding this comment

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

In previous iterations of ARC60 the requirement was stated that it must be possible to sign exactly the input data (e.g., a 32 byte hash). Here the input data is hashed before it is signed, which means it's not possible to sign exactly the input data. Is that requirement no longer necessary? Because that'd be great and would simplify things a lot compared to the past.

One concern that I have is that the resulting hash might still by chance (or by design from a bad actor) contain a Program prefix, which makes the hash a valid logic signature. In the worst case, a logicsig could be crafted that takes control of the user's account.

D13 explained that a future version of this ARC will explicitly prevent Program prefixes, which is great.

Another option is to add a fixed prefix like so: EdDSA('arc60' + SHA256(data) + SHA256(authenticatedData)). Since we already modify the user's input before signing, I don't see a problem with adding a prefix.

Copy link
Author

Choose a reason for hiding this comment

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

"prefix" are a really bad practice in my opinion; prevents any subject to validate signature crafted in algorand wallets / dapps and generically verify them without conforming to our own custom way of doing things.

We can add a check to prevent "Program"; sure. But the chances of you finding a hash with valid 7 bytes (Program) + the remainder of the program are extremely low.

Still, i can add a check to reject "Program"

| --- | --- | --- |
| `data` | `string` | string representing the content to be signed for the specific `Scope`. This can be an encoded JSON object or any other data. It **MUST** be presented to the user in a human-readable format. |
| `signer` | `bytes` | public key of the signer. This can the public related to an Algorand address or any other Ed25519 public key. |
| `domain` | `string` | This is the domain requesting the signature. It can be a URL, a DID, or any other identifier. It **MUST** be presented to the user to inform them about the context of the signature. |
Copy link
Contributor

Choose a reason for hiding this comment

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

Must the domain also be matched against WalletConnect, for example? This means, if a user is connected to dapp example.com through her wallet and the dapp asks the wallet to sign a payload, does the wallet have to check that example.com is the domain in StdSignData?

Copy link
Author

Choose a reason for hiding this comment

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

Yup :)

const authenticationData: Uint8Array = new Uint8Array(createHash('sha256').update(caip122Request.domain).digest())

const signData: StdSigData = {
data: Buffer.from(JSON.stringify(caip122Request)).toString('base64'),
Copy link
Contributor

Choose a reason for hiding this comment

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

Is JSON.stringify deterministic? Since JSON objects are unordered, we might have to resort to something like canonical JSON to derive a deterministic byte-array from JSON data suitable for signing.

Copy link
Author

@ehanoc ehanoc Oct 11, 2024

Choose a reason for hiding this comment

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

Great point. Yes, it should be deterministic JSON

Copy link
Author

Choose a reason for hiding this comment

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

Updated @k13n

@emg110
Copy link
Contributor

emg110 commented Oct 22, 2024

on custom (Algorand ecosystem internal usage) AUTH scope and custom auth message, this is 100% good to go and flawless, IMHO.
But for other sub-scopes of AUTH (FIDO2, CAIP122,...):

  • I think the FIDO2 and WebAuthn scopes need test cases to be verified by standard well-known libraries (which is the case when these scopes are used by developers), e.g. "fido2-lib" and "jsonwebtoken".
  • I would like to add to the list above OAuth2 and OIDC under which the token to be signed is actually two base64 strings concatenated by a ".", which has not been seen on the sign function (the second base64 after split by "." will be the payload to sign). For these as well, verification by standard well-known libraries would be nice to be added.

On the other hand, now that we have extension mechanisms in ARCs, may I suggest this ARC-60 to be limited only to custom and random arbitrary data signing (for which it is complete and good to go final IMHO) and then in the future after we add successful verification with standard well-known libraries on each sub-scope, we add those as extensions to this ARC. E.g. for Fido2/WebAuthn, for OAuth2/OIDC, and for simple JWT/JWS.
This way everyone who needs this ARC for simple AUTH scope usage would be satisfied and also the more complex use-cases like those subscopes can enjoy more attention and preparation to be more complete and verified by well-known standard tools as well to make sure the standard fits the requirement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants