# Algorand Wallet Arbitrary Signing API

## Abstract

This ARC proposes a standard for arbitrary data signing. It is designed to be a simple and flexible standard that can be used in a wide variety of applications.

## 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 [RFC-2119](https://www.ietf.org/rfc/rfc2119.txt).

> Comments like this are non-normative

## Rationale

Signing data is a common and critical operation. Users may need to sign data for multiple reasons (e.g. delegate signatures, DIDs, signing documents, authentication).

Algorand wallets need a standard approach to byte signing to unlock self-custodial services and protect users from malicious and attack-prone signing workflows.

This ARC provides a standard API for bytes signing. The API encodes byte arrays to be signed into well-structured JSON schemas together with additional metadata. It requires wallets to validate the signing inputs, notify users about what they are signing and warn them in case of dangerous signing requests.

### Overview

This ARC defines a function `signData(signingData, metadata)` for signing data.

`signingData` is a `StdSigData` object composed of the signing `data` that instantiates a known JSON Schema and the `signer`’s public key.

### Signing Flow

When connected to a specific `domain` (i.e app or other identifier), the wallet will receive a request to sign some `data` along side some `authenticatorData`, which will look like some random bytes. With this information, the wallet should follow the following steps:

1. Hash the `data` field with `sha256`.
2. Knowing to what `domain` we are connected to, hash such value with `sha256` and compare it with the first 32 bytes of `authenticatorData`.
   2.1. If the hashes do not match, the wallet **MUST** return an error.
3. Append the `authenticatorData` to the resulting hash of the `data` field.
4. Sign the result

### `Scopes`

Supported scopes are:

- `AUTH` (1): This scope is used for authentication purposes. It is used to sign data that will be used to authenticate the user to a specific domain. The `data` field **MUST** be a JSON object that represents the content to be signed. The `authenticatorData` field **MUST** include, at least, the `sha256` hash of the `domain` requesting a signature. The wallet **MUST** do an integrity check on the first 32 bytes of `authenticatorData` to match the hash. The `hdPath` field is **optional** and **MUST** be a BIP44 path in order to derive the private key to sign the `data`. The wallet **MUST** validate the path before signing.

Summarized signing process for `AUTH` scope:

```
EdDSA(SHA256(data) + SHA256(authenticatorData))
```

- **`note`**: Other scopes could be added in the future.

#### Parameters

##### `StdSigData`

Must be a JSON object with the following properties:

| Field | Type | Description |
| --- | --- | --- |
| `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` | This field is **optional**. It is used to identify the request. It **MUST** be unique for each request. |
| `authenticatorData` | `bytes` | It **MUST** include, at least, the `sha256` hash of the `domain` requesting a signature. The wallet **MUST** do an integrity check on the first 32 bytes of `authenticatorData` to match the hash. It **MAY** 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 `authenticatorData` before signing. Each `Scope` **MUST** specify if `authenticatorData` should be appended to the hash of the `data` before signing. |
| `hdPath` | `string` | This field is **optional**. It is required if the wallet supports BIP39 / BIP32 / BIP44. This field **MUST** be a BIP44 path in order to derive the private key to sign the `data`. The wallet **MUST** validate the path before signing. |

##### `metadata`

Must be a JSON object with the following properties:

| Field | Type | Description |
| --- | --- | --- |
| `scope` | `integer` | Defines the purpose of the signature. It **MUST** be one of the following values: `1` (AUTH) |
| `encoding` | `string` | Defines the encoding of the `data` field. `base64` is the recommended encoding. |

##### `authenticatorData`

| Name | Length | Description | optional |
| --- | --- | --- | --- |
| `rpIdHash` | 32 bytes | SHA256 hash of the domain requesting the signature. | No |
| `flags` | 1 byte | Flags (bit 0 is the least significant bit): <br> \- 0x01: User Present (UP) <br> \- 0 means the user is not present. <br> Bit 1 Reserved for future use (RFU1). <br> Bit 2 User Verified (UV) result. <br> \- 1 means user is verified. <br> \- 0 means user is not verified. Bits 3 - 5 Reserved for future use (RFU2). <br> Bit 6: Attested credential data included (AT). <br> \- Indicates whether the authenticator added attested credential data. <br> Bit 7: Extension data included (ED). <br> \- Indicates whether the authenticator added extension data. | yes |
| `signCount` | 4 bytes | Signature counter. <br> \- This is a monotonically increasing counter that is incremented each time the user successfully authenticates. <br> \- The counter is reset to 0 when the authenticator is reset. <br> \- The counter is used to prevent replay attacks. | Yes |
| `attestedCredentialData` | variable | attested credential data (if present). See [Specification](https://www.w3.org/TR/webauthn-2/#sctn-attested-credential-data) | Yes |
| `extensions` | variable | extension data (if present), is a key value JSON structure that may or may not be included. See [Specification](https://www.w3.org/TR/webauthn-2/#sctn-extensions) for full details | Yes |

This follows the FIDO WebAuthn specification for the `authenticatorData` field. The wallet **MUST** validate the `authenticatorData` field before signing. For more information on the `authenticatorData` field, please refer to the [WebAuthn specification](https://www.w3.org/TR/webauthn-2/#authenticator-data).

##### `Errors`

These are the possible errors that the wallet **MUST** handle:

| Error | Description |
| --- | --- |
| `ERROR_INVALID_SCOPE` | The `scope` is not valid. |
| `ERROR_FAILED_DECODING` | The `data` field could not be decoded. |
| `ERROR_INVALID_SIGNER` | Unable to find in the wallet the public key related to the signer. |
| `ERROR_MISSING_DOMAIN` | The `domain` field is missing. |
| `ERROR_MISSING_AUTHENTICATED_DATA` | The `authenticatorData` field is missing. |
| `ERROR_BAD_JSON` | The `data` field is not a valid JSON object. |
| `ERROR_FAILED_DOMAIN_AUTH` | The `authenticatorData` field does not match the hash of the `domain`. |
| `ERROR_FAILED_HD_PATH` | The `hdPath` field is not a valid BIP44 path. |

## Backwards Compatibility

N / A

## Reference Implementation

Available in the `assets/arc-0060` folder.

### Sample Use cases

#### Generic AUTH

```
    const authData: Uint8Array = new Uint8Array(createHash('sha256').update("arc60.io").digest())

const authRequest: StdSigData = {

data: Buffer.from("{[jsonfields....]}").toString('base64'),

signer: publicKey,

domain: "arc60.io",

requestId: Buffer.from(randomBytes(32)).toString('base64'),

authenticationData: authData,

hdPath: "m/44'/60'/0'/0/0"

}

const signResponse = await arc60wallet.signData(authRequest, { scope: ScopeType.AUTH, encoding: 'base64' })
```

#### CAIP-122

```
    const caip122Request: CAIP122 = {

domain: "arc60.io",

chain_id: "283",

account_address: ...

type: "ed25519",

statement: "We are requesting you to sign this message to authenticate to arc60.io",

uri: "https://arc60.io",

version: "1",

nonce: Buffer.from(randomBytes(32)).toString,

...

}

// Disply message title according EIP-4361

const msgTitle: string = `Sign this message to authenticate to ${caip122Request.domain} with account ${caip122Request.account_address}`

// Display message body according EIP-4361

const msgBodyPlaceHolders: string = `URI: ${caip122Request.uri}\n` + `Chain ID: ${caip122Request.chain_id}\n`
        + `Type: ${caip122Request.type}\n`
        + `Nonce: ${caip122Request.nonce}\n`
        + `Statement: ${caip122Request.statement}\n`
        + `Expiration Time: ${caip122Request["expiration-time"]}\n`
        + `Not Before: ${caip122Request["not-before"]}\n`
        + `Issued At: ${caip122Request["issued-at"]}\n`
        + `Resources: ${(caip122Request.resources ?? []).join(' , \n')}\n`

// Display message according EIP-4361

const msg: string = `${msgTitle}\n\n${msgBodyPlaceHolders}`

console.log(msg)

// authenticationData

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

const signData: StdSigData = {

data: Buffer.from(JSON.stringify(caip122Request)).toString('base64'),

signer: publicKey,

domain: caip122Request.domain, // should be same as origin / authenticationData

// random unique id, to help RP / Client match requests

requestId: Buffer.from(randomBytes(32)).toString('base64'),

authenticationData: authenticationData

}

const signResponse = await arc60wallet.signData(signData, { scope: ScopeType.AUTH, encoding: 'base64' })

expect(signResponse).toBeDefined()

// reply
```

## Security Considerations

Wallets are free to make their own UX choices, but they **SHOULD** show the user the purpose (i.e. `scope`) of the signature, the domain that is requesting the signature, and the data that is being signed. This is to prevent users from signing data that they do not understand.

Additionally, wallets **MUST** show to the user the data that is being signed in a human-readable format, as well as the authenticatorData and how it was calculated, so that the hash can be verified by the user when signing with ledger for example.

## Copyright

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