---
id: TIP-1053
title: Nonce in Key Authorizations
description: Add an optional 32-byte nonce to key authorizations so a single signature can both authorize an access key and bind to an arbitrary application context, with protocol-enforced uniqueness enabling revocation.
authors: Jake Moxey (@jxom)
status: Approved
related: TIP-1011, TIP-1020
protocolVersion: T5
---

# TIP-1053: Nonce in Key Authorizations

## Abstract

This TIP extends the `key_authorization` payload with an optional `nonce: bytes32` field. The protocol includes the nonce in the signing hash and enforces per-account uniqueness so a nonce can be consumed at most once. The nonce value itself is otherwise opaque to the protocol. Applications use the field to bind a single signature to an arbitrary context — most commonly a server-issued  challenge — so a user can authorize an access key and prove identity to an application in one signature. The same field doubles as a revocation handle for previously signed but not-yet-submitted authorizations.

## Motivation

A common UX pattern requires two signatures from the user:

1. **Authenticate to an application server.** The server issues a random challenge; the user signs it with their account key to prove possession.
2. **Authorize a new access key.** The user signs a `key_authorization` so the application can act on their behalf going forward (subject to limits/scopes/expiry).

On WebAuthn keys, each signature is a separate biometric prompt. Doing this twice for what users perceive as a single "log in" action is awkward and erodes trust.

Adding a `nonce` to `key_authorization` lets the user sign **once**: the signature simultaneously authorizes the access key and binds to the offchain verifier's challenge. An offchain verifier (most commonly an application server or any other party that wants to validate the user's intent) checks the binding by recomputing the expected nonce from the challenge it issued and comparing it to the value committed to in the signed authorization.

A `nonce` field also gives accounts a first-class revocation primitive: an account can burn a nonce onchain (via a no-op consumption) to invalidate any previously signed but unsubmitted `key_authorization` that uses that same nonce. This is particularly useful when a user signs a key authorization that is then lost, leaked, or superseded before it lands on chain.

The protocol stays minimal: it does not learn about sign-in semantics, application schemas, or challenge formats. Apps and wallets standardize on the nonce format. The protocol's only added responsibility is uniqueness tracking.

## Assumptions

- The offchain verifier (e.g. app server) is responsible for replay protection of its own session flow. The protocol's nonce uniqueness check protects the onchain registration of the **key authorization**; an offchain verifier still needs to bind its session to the specific nonce it issued.
- The `nonce` format is application-defined when used as a challenge digest. The protocol does not validate its structure.
- This TIP is purely additive to `key_authorization`. Existing authorizations (without the field) continue to validate identically. The field defaults to absent / zero, in which case the resulting hash is byte-equivalent to pre-TIP-1053 encoding for that authorization and no nonce tracking is performed.
- Nonce-bearing authorizations gain an additional uniqueness guarantee at the `(account, nonce)` granularity.

---

# Specification

## Encoding

`key_authorization` gains an optional trailing `nonce: bytes32` field:

```
key_authorization = rlp([
  chain_id,
  key_type,
  key_id,
  expiry?,
  limits?,
  allowed_calls?,
  nonce?,  // NEW — 32 bytes, default zero/absent
])
```

- Encoding is RLP, identical to the existing payload format with one optional trailing item.
- `nonce` MUST be exactly 32 bytes when present.
- A `nonce` of `bytes32(0)` is reserved to mean "no nonce" and MUST be omitted from the RLP encoding rather than encoded as 32 zero bytes. This guarantees byte-equivalence with pre-TIP-1053 encoding for nonce-less authorizations.

## Signing

The signing hash is the keccak256 of the canonical RLP encoding:

```
digest = keccak256(rlp(key_authorization))
```

No additional domain separation is introduced. RLP boundaries already disambiguate the trailing field. The protocol verifies signatures exactly as it does today; the only change is that the encoded payload may now contain the additional field.

## Protocol Behavior

The protocol:

- MUST include `nonce` in the signing hash whenever it is present in the encoded `key_authorization`.
- MUST enforce that, for any account `A`, a given non-zero `nonce` is consumed at most once. An attempt to register a `key_authorization` whose `(account, nonce)` pair has already been consumed MUST revert.
- MUST mark `(account, nonce)` as consumed atomically with the successful registration of the authorization.
- MUST NOT enforce ordering, monotonicity, or any other structural constraint on `nonce`. Nonces may be arbitrary 32-byte values chosen by the application.
- MUST NOT interpret the `nonce` value beyond uniqueness tracking.
- MUST NOT emit `nonce` in any event by default beyond what is needed to expose consumption.

A nonce-less authorization (field absent / `bytes32(0)`) is processed exactly as in pre-TIP-1053 behavior: no uniqueness check is performed, no state is written for nonce tracking, and the existing `keys[account][key_id]` permanence is the sole replay guard.

The field is invisible to existing precompile read paths that surface `KeyInfo`. A new read path MAY be added to query consumption status of a given `(account, nonce)` pair.

## Revocation

An account holder can pre-emptively invalidate a signed-but-unsubmitted `key_authorization` by burning its nonce onchain. Burning is a no-op consumption: the protocol exposes a path that marks `(account, nonce)` as consumed without registering any key. Once the nonce is consumed, any later attempt to submit the original authorization with the same nonce reverts.

This gives users and wallets a clean recovery primitive when:

- A signed authorization is lost or leaked before submission.
- A user wants to supersede an outstanding authorization with a new one for the same `key_id`.

Burning is restricted to the account itself (or any key authorized to act on its behalf, subject to that key's scope rules).

## Verification

A offchain verifier (most commonly an application server, but the same flow applies to any party validating the signed authorization) verifies a nonce-bearing authorization entirely offchain. The client sends the verifier a single byte string:

```
payload             = key_authorization ‖ signature

key_authorization   = rlp([chain_id, key_type, key_id, expiry, limits, allowed_calls, nonce])
signature           = root or admin key signature over keccak256(key_authorization)
```

The verifier:

1. Splits `payload` into `key_authorization` and `signature` (signature length is fixed per signature type).
2. RLP-decodes `key_authorization` to access its fields.
3. Recomputes `expected_nonce` from the challenge / typed-data message it issued.
4. Asserts the decoded `nonce == expected_nonce`.
5. Hashes `key_authorization` with keccak256, recovers the signer from `signature`.
6. Asserts the recovered signer equals the expected account.
7. Validates the remaining decoded fields against the verifier's policy (key_id matches the passkey being registered, expiry is within bounds, limits/scopes match the verifier's expectations).

## Format

The `nonce` format is application-defined when used as a challenge digest. The protocol treats any 32-byte value identically and does not prescribe a format. Wallet and application standardization (e.g., a "Sign-In with Tempo" convention) is left to a separate document so it can evolve without protocol changes.

## Backward Compatibility

- Existing key authorizations without the field continue to validate. The encoder MUST omit the trailing field when `nonce == bytes32(0)`, producing a byte-equivalent encoding to pre-TIP-1053 payloads.
- Legacy verifiers that don't understand the field will compute the same hash for legacy-shaped authorizations and will reject (signature mismatch) for authorizations with a non-zero nonce, which is the correct fail-safe.
- Nonce uniqueness tracking is only engaged for non-zero nonces; legacy nonce-less authorizations incur no additional state.

# Invariants

1. **Hash inclusion.** When `nonce` is present in the encoded `key_authorization`, it MUST be part of the signing hash.

2. **Encoding determinism.** A `nonce` of `bytes32(0)` MUST be omitted from the RLP encoding, never encoded as 32 zero bytes.

3. **Single-use.** For any account `A` and any non-zero `nonce` value `n`, the protocol MUST accept at most one successful `key_authorization` registration or burn for `(A, n)`.
