---
id: TIP-1099
title: RLP-Encoded Account Keychain Mutations
description: Removes direct key authorization and replaces nested ABI call-scope decoding with RLP.
authors: Arsenii Kulikov (@klkvr)
status: Draft
related: TIP-1011, TIP-1049, TIP-1100, https://gist.github.com/rappie/094e3a9f786a0a6766b4b0df9805fe0b
protocolVersion: T11
---

# TIP-1099: RLP-Encoded Account Keychain Mutations

## Abstract

This TIP removes the `AccountKeychain.authorizeKey` and `authorizeAdminKey`
functions. All keys must instead be authorized through a transaction's
`key_authorization` field.

It also changes `setAllowedCalls` to accept RLP-encoded call scopes instead of
nested ABI arrays. This preserves permission updates for existing keys without
the complexity of decoding nested ABI input in a precompile.

## Motivation

Nested ABI structures in precompile interfaces can be abused to force increased
CPU and memory usage during decoding. This TIP removes them from
`AccountKeychain` by reusing the canonical RLP encodings already used in Tempo
transactions.

Worst-case RLP decoding measured about 400 MB/s, or 12.5 million 32-byte words
per second. Pricing this work at 1 GGas/s requires 80 gas per word. TIP-1100
charges 30 gas per word of precompile input, so this TIP charges another 50 gas
per word of RLP input.

## Assumptions

- `key_authorization` can represent every key configuration currently accepted
  by `authorizeKey` and `authorizeAdminKey`.
- TIP-1100 activates no later than this TIP.
- Existing keys and stored call scopes remain unchanged at activation.

---

# Specification

## Remove direct key authorization

The following overloads are disabled at activation:

```solidity
authorizeKey(address, SignatureType, uint64, bool, LegacyTokenLimit[])
authorizeKey(address, SignatureType, KeyRestrictions)
authorizeKey(address, SignatureType, KeyRestrictions, bytes32)
authorizeAdminKey(address, SignatureType, bytes32)
```

Their selectors must revert as unknown function selectors without changing
state. All keys, including admin keys, must be authorized through transaction
`key_authorization`.

All other keychain methods are unchanged.

## Change `setAllowedCalls`

Replace:

```solidity
function setAllowedCalls(address keyId, CallScope[] calldata scopes) external;
```

with:

```solidity
/// @notice Creates or replaces call scopes for an existing key.
/// @param keyId The key whose scopes are updated.
/// @param scopes Canonical RLP encoding of a non-empty CallScope list.
function setAllowedCalls(address keyId, bytes calldata scopes) external;
```

The old selector must revert as an unknown function selector after activation.

`scopes` uses the existing TIP-1011 schema:

```text
AllowedCalls := RLP([CallScope, ...])

CallScope := RLP([
    target: address,
    selector_rules: [SelectorRule, ...] | []
])

SelectorRule := RLP([
    selector: bytes4,
    recipients: [address, ...] | []
])
```

The input must contain exactly one canonical, non-empty `AllowedCalls` list.
Malformed RLP, trailing bytes, incorrect field lengths, and an empty outer list
must revert.

Before decoding `scopes`, deduct:

```text
rlp_words = ceil(scopes.length / 32)
rlp_cost = rlp_words * 50
```

This charge is in addition to TIP-1100's 30 gas per word of precompile input. If
the caller has insufficient gas, execution must halt with out-of-gas before RLP
decoding.

All TIP-1011 rules remain unchanged, including uniqueness checks, valid
recipient-aware selectors, TIP-20 target checks, and create-or-replace behavior
per target. The complete input must be decoded and validated before any storage
write so failures are atomic.

`removeAllowedCalls(address,address)` remains the way to remove a target scope.
An empty `selector_rules` list continues to allow every selector for that target.

## Activation

The selector changes are hardfork-gated. Historical blocks continue to use the
old interfaces.

# Tooling

SDKs and wallets must use transaction `key_authorization` instead of constructing
`authorizeKey` or `authorizeAdminKey` calls. Call-scope builders should RLP-encode
`CallScope` values and construct the new `setAllowedCalls(address,bytes)` call.

# Observability

No new events are required. Failed decoding and disabled selectors are visible
as reverted precompile calls.

# Invariants

1. The removed selectors cannot modify keychain state after activation.
2. Invalid RLP causes no storage writes.
3. Valid RLP produces the same scope state as the old ABI method for the same
   logical input.
4. Existing keys and scopes are unchanged at activation.
5. Existing TIP-1011 call-scope semantics are unchanged.
