---
id: TIP-1159
title: Attested Signers (Meta)
description: Tempo signers controlled by approved code in an attested trusted execution environment, which signs for a user after any supported sign-in method, so no operator key or operator decision can sign alone.
authors: Jake Moxey (@jxom)
status: Draft
related: TIP-1160, TIP-1161, TIP-1162, TIP-1163, TIP-0001, TIP-1011, TIP-1020, TIP-1045, TIP-1067
protocolVersion: TBD
---

# TIP-1159: Attested Signers (Meta)

## Abstract

This TIP adds attested signers: Tempo addresses that sign through a signer program in a trusted execution environment (TEE). A user signs in with any method the program supports, such as an email code or an OpenID Connect provider, and the sign-in binds a key on the user's device. After that, the program signs any digest for the user when it receives a proof from that device and an approval from the wallet operator. The chain accepts the signature only from a key that an attestation service certified as belonging to approved code. Each signature costs one P256 verification and two storage reads.

Component TIPs define the signature type, the key registry, and one attestation scheme each for AWS Nitro Enclaves and Google Cloud Confidential Space. The design trusts the TEE hardware, the attestation service, the approved code, and the identity method the user signs in with.

## Motivation

Wallets that offer "Continue with email" or "Continue with Google" run a server that decides when a user signed in. The server then signs for the user, either with a key it holds or with a token that a custodian accepts. Whoever controls that server controls every account:

- a breach of the server can sign as every user at once;
- an insider or a bad deploy can do the same, with no visible change;
- users cannot tell what the server signs for them until it is onchain.

Moving the key into a key management service (KMS) prevents theft of the key, but the server still decides when to use it. The risk lies in that decision.

An attested signer moves the decision into a program whose exact code the chain can identify. A TEE runs the program in memory that the host cannot read or change. An attestation service certifies a measurement of the program, a hash of its code, together with a key that the program generated. The chain accepts that key only if a policy lists the measurement. Different code has a different measurement, so a compromised operator cannot produce an accepted key.

The signer program checks how the user signed in. The chain sees only a policy ID and a user tag, so a wallet can add a sign-in method by approving new code, with no protocol change.

### Alternatives considered

- **A zero-knowledge proof of the identity provider's token:** removes the operator for providers that sign tokens with a supported algorithm. It cannot prove an email code or a method without a signed token, and the token issuer can still sign as all of its users. Each proof needs a prover and a trusted setup, and costs about 350,000 gas to verify.
- **A custodian with an operator-signed token:** the custodian and the operator can each sign as every user, and neither can be observed.
- **A new attestation document with every signature:** removes the registered key, but documents are several kilobytes, beyond [TIP-1045](./tip-1045.md)'s 1,024-byte key-authorization bound. A Nitro document costs about 790,000 gas to verify ([TIP-1162](./tip-1162.md#gas)).
- **Signing only at sign-in, then access keys alone:** a key authorization at sign-in, then an access key for every later operation. It needs access keys with broad permissions for operations that only the account's owner can do. The program signs each operation instead, and access keys stay optional.
- **Validators checking sign-ins:** adds validator duties and makes the network choose identity methods.
- **Provider keys fixed in measured code:** every provider key rotation becomes a new measurement, which waits for the policy's delay, and sign-ins fail in the meantime.
- **Provider keys as policy trust anchors, as in [TIP-1163](./tip-1163.md#trust-anchors):** each rotation still waits for the policy's delay. TIP-1163 can do this because Google Cloud Attestation's keys are few and change rarely. OpenID Connect providers rotate more often and do not promise lead time.
- **Provider keys from [TIP-1132](https://tips.sh/1132)'s Key Publisher:** the program would need to verify Tempo state, which needs a light client in the TEE, and it adds trust in the publisher owner.

## Assumptions

- **TEE integrity.** The TEE keeps the program's memory confidential and runs only the measured code. A break exposes the program key and the program's secrets until the break is fixed.
- **Attestation service honesty.** The service certifies only measurements it observed. A dishonest or compromised service can certify any measurement, so it can sign for every address of every policy that trusts it.
- **Approved code correctness.** The program signs only with a valid device binding, a valid device proof, and a valid operator approval for the same digest. A bug in approved code can let anyone sign as any user of the policy until the policy removes the measurement.
- **Policy owner diligence.** The owner proposes only measurements that reproducible builds of reviewed source produce. The delay before a measurement becomes active gives others time to check this. A compromised owner can approve malicious code after the delay.
- **Identity methods.** Each identity method is as safe as its weakest party, such as the user's mailbox and the program's email account, or the OpenID Connect provider. A compromised method lets the attacker bind a device as any user of that method.
- **Provider key transport.** The root certificates that the program fixes for each provider's `jwks_uri` host issue certificates for that host only to its owner. A misissued certificate for that host lets its holder serve keys to the program, and so bind a device as any user of that method.
- Configurable accounts (TIP-1107 and TIP-1114, in draft) are not required. Where they are active, attested signers can be owners.

---

# Specification

## Overview

### Terms

| Term | Meaning |
|---|---|
| Signer program | The program that checks sign-ins and signs. It runs in a TEE. |
| Measurement | A 32-byte value that identifies the program's exact code, derived by the attestation scheme. |
| Attestation document | Evidence from an attestation service that a TEE runs code with a given measurement and holds a given key. |
| Attestation scheme | The rules that verify one service's documents ([TIP-1162](./tip-1162.md) for AWS Nitro Enclaves, [TIP-1163](./tip-1163.md) for Google Cloud Confidential Space). |
| Program key | A P256 key that the signer program generates and registers with an attestation document. |
| Policy | An onchain record naming one scheme, its approved measurements, its trust anchors, an owner, a guardian, a delay, and a key lifetime cap ([TIP-1161](./tip-1161.md)). |
| Key slot | One of a policy's eight places for a program key. A registration into a slot replaces the key it held. |
| Key lifetime cap | The longest time a registration lasts under a policy, chosen by its owner, at most 7 days. |
| Identity method | A way to prove an identity to the program, such as an email code, an OpenID Connect ID token, or a wallet signature. The program defines each method. |
| Identity | A method name and a normalized identifier, such as `email:alice@example.com` or `google:<sub>`. |
| User tag | A 32-byte value that the program derives from an identity with its secret `k` ([Identity methods](#identity-methods)). The chain treats it as opaque. |
| Device key | A P256 key that the user's client generates at each sign-in. It never leaves the client. It can also be an access key of the account ([Access keys](#access-keys)). |
| Device binding | A record, issued by the program at sign-in, that binds one device key to one user tag until it expires. |
| Device proof | A signature by the device key over one digest ([reference profile](#reference-authorization-profile)). |
| Wallet operator | The party that runs the wallet's servers and its signer program, serves the wallet client's code, and approves each signature. |
| Operator approval | A signature by the wallet operator over one digest for one device. |
| Attested signer | The address derived from a policy ID and a user tag ([TIP-1160](./tip-1160.md#address-derivation)). |

### Lifecycle

1. **Approval.** A policy owner proposes a measurement. After the policy's delay, the measurement is active.
2. **Registration.** The program generates a program key and obtains an attestation document that binds the key to this chain, the registry, the policy, a key slot, and a lifetime. Anyone submits it to `registerKey`. The registry verifies it under the policy's scheme and writes the key into the slot, which ends the key the slot held. The key lasts for the requested lifetime, up to the key lifetime cap. The program rotates its key at an interval it chooses, alternating between two slots.
3. **Sign-in.** The client generates a device key. The user proves an identity with one identity method, and the proof commits to the device key. The program checks the proof, derives the user tag, and issues a device binding.
4. **Signing.** For each operation, the client signs a device proof over the digest, and the wallet operator approves the same digest. The program checks the binding, the device proof, and the approval, then signs the digest with its program key.
5. **Verification.** The chain checks that the key slot the signature names holds the program key as current, verifies the signature, and derives the signer address from the policy ID and user tag.

Steps 1, 2, and 5 are consensus rules. Steps 3 and 4 are rules of the measured code, which the [reference authorization profile](#reference-authorization-profile) describes.

The critical authority boundary is the measurement check at registration. Only code whose measurement the policy approved can hold a registered program key, and only a registered program key can sign for the policy's addresses. Inside that code, the second boundary is the pair of device proof and operator approval: neither the device nor the operator gets a signature alone.

### Scenario

The following sections follow one illustrative user. Alice signs in to a wallet with the email address `alice@example.com` under policy `P`, whose signer program runs in an AWS Nitro Enclave. Each section covers one step of the [lifecycle](#lifecycle).

### Program key registration

The signer program keeps a current program key in the registry, independent of any user. It uses two of the policy's key slots, `0` and `1` in this scenario, and writes them in turn. Each rotation overwrites the slot that holds the older key, so the newer key in the other slot stays current throughout.

```mermaid
sequenceDiagram
    autonumber
    participant S as Signer program (TEE)
    participant A as Attestation service
    participant T as Tempo

    loop Every rotation interval, slot = 0, 1, 0, 1, ...
        S->>S: programKey = p256.generate()
        S->>A: Request a document binding programKey to chain, registry, P, slot, lifetime
        A-->>S: Attestation document (measurement, binding)
        S->>T: registerKey(P, slot, document, programKey, lifetime)
        T->>T: Verify the document, check the measurement is active in P
        T->>T: Overwrite the slot, which ends its earlier key
        T-->>S: Registered until now + min(lifetime, key lifetime cap)
        S->>S: Sign with programKey, the newest current key
    end
```

1. The program generates a new program key, and picks the slot that holds its older key.
2. The attestation service returns a document that binds the key to the chain, the registry, policy `P`, the slot, and the requested lifetime, with the measurement of the running code.
3. The program, or anyone, submits the document to `registerKey` ([TIP-1161](./tip-1161.md#registration)).
4. The registry verifies the document under the policy's scheme, checks that the measurement is active, and writes the key into the slot until `now + min(lifetime, key lifetime cap)`. The slot's earlier key stops being current at once.
5. The program signs with the new key from then on. The key in the other slot stays current, so signatures already in flight still verify until their own `valid_until`.
6. At the next rotation, the program writes the other slot.

The operator chooses the rotation interval and the lifetime, within the key lifetime cap. The lifetime covers at least two rotation intervals, so the older key is still current when the next rotation replaces it. In this scenario, the program rotates every hour with a two-hour lifetime.

### Sign-in

Alice proves her identity once, and the proof binds a new device key to her user tag. Email is one identity method. Another method changes only the challenge and the proof in steps 5 to 9.

```mermaid
sequenceDiagram
    autonumber
    actor U as Alice
    participant W as Wallet
    participant O as Wallet operator
    participant S as Signer program (TEE)

    U->>W: Continue with alice@example.com
    W->>W: deviceKey = p256.generate()
    W->>O: Start sign-in (email, deviceKey)
    O->>S: Start sign-in (email, deviceKey)
    S->>U: Email code
    U->>W: Code
    W->>O: Code
    O->>S: Finish sign-in (code, deviceKey)
    S->>S: Check the code<br/>userTag = HMAC-SHA256(k, "tempo:user-tag:v1" || "email:alice@example.com")
    S-->>O: Device binding for deviceKey and userTag
    O-->>W: Session for the device
```

1. Alice's wallet generates a device key for this sign-in.
2. The wallet starts the sign-in through the wallet operator.
3. The signer program sends Alice an email code from its own email account. The challenge commits to the device key.
4. Alice enters the code, and the wallet returns it through the operator.
5. The program checks the code, derives Alice's user tag ([Identity methods](#identity-methods)), and issues a device binding for the device key.
6. The operator records the device for Alice's session.

If Alice enters a wrong code, the program issues no binding.

### Signing

Each later operation needs the device key and the operator's approval, with no new sign-in.

```mermaid
sequenceDiagram
    autonumber
    actor U as Alice
    participant W as Wallet
    participant O as Wallet operator
    participant S as Signer program (TEE)
    participant T as Tempo

    U->>W: Send 10 USD to Bob
    W->>W: digest = signing hash of the transaction<br/>deviceProof = deviceKey.sign(digest, nonce)
    W->>O: Transaction and deviceProof
    O->>O: Check the session and policy, approve
    O->>S: digest, deviceProof, approval, binding
    S->>S: Check the binding, the device proof, and the approval
    S-->>O: Attested signature over digest
    O->>T: Transaction from attested_address(P, userTag)
    T->>T: Check the key slot and policy<br/>Verify the P256 signature, derive the address
    T-->>W: Receipt
```

1. The wallet computes the operation's digest and signs a device proof over it with a fresh nonce.
2. The wallet operator checks Alice's session and its own policy, and approves the same digest and nonce.
3. The signer program checks the binding, the device proof, and the approval, then signs the digest ([TIP-1160](./tip-1160.md#signed-digest)).
4. The transaction is sent from Alice's attested signer address. The node verifies the attested signature ([TIP-1160](./tip-1160.md#verification)).

The same path signs key authorizations, configurable-account owner approvals, and messages. The operator can forward each operation in one round trip from the wallet.

| Failure | Outcome |
|---|---|
| The device proof or the approval is missing or names another digest | The program signs nothing |
| The program key expires, or a rotation replaces it, before the transaction lands | The node rejects the signature, and the wallet requests a new one |

Revoking a device needs no state in the program. Every signature needs the operator's approval, so the operator revokes a device by refusing to approve its operations. The binding token itself cannot be revoked. It only expires.

### Access keys

The device key can also be an access key of Alice's account. One key then serves both roles, and transactions within the key's limits need no request to the operator or the signer program.

```mermaid
sequenceDiagram
    autonumber
    participant W as Wallet
    participant O as Wallet operator
    participant S as Signer program (TEE)
    participant T as Tempo

    Note over W,T: Once, at sign-in or later
    W->>W: keyAuthorization for deviceKey (limits, expiry)<br/>deviceProof over its digest
    W->>O: keyAuthorization and deviceProof
    O->>S: digest, deviceProof, approval, binding
    S-->>O: Attested signature over the key authorization
    O->>T: Transaction carrying the key authorization
    T->>T: Store deviceKey as an access key

    Note over W,T: Each transaction within the limits
    W->>W: Sign the transaction with deviceKey
    W->>T: Transaction from attested_address(P, userTag), keychain signature
    T->>T: Check the access key and its limits
```

1. The wallet builds a key authorization for its device key, with the limits and expiry the app requested, and signs a device proof over its digest.
2. The operator approves, and the signer program signs the key authorization as the account's owner. This follows the [Signing](#signing) path, and it can share the sign-in's request.
3. The chain stores the device key as an access key of the attested signer's account ([TIP-1011](./tip-1011.md)).
4. Within the key's limits, the device key signs transactions directly as a keychain signature. No request reaches the operator or the signer program.
5. Beyond the limits, and for operations that only the owner can do, the device key signs a device proof instead, and the [Signing](#signing) path applies.

| Rule | Reason |
|---|---|
| The key authorization names the device key, and no other key | One key per sign-in, so revoking the device covers both roles |
| A key authorization with no limits and no allowed calls grants no direct power | The device key then acts only through the operator's approval |
| Device proofs and keychain signatures sign different preimages | A device proof never verifies as a transaction signature, and the reverse |

Revoking the access key onchain ends its direct power. The operator's refusal ends its owner operations. A wallet that revokes a device does both.

### Trust

Each party's authority, and its limit:

| Party | Can | Cannot |
|---|---|---|
| Attestation service | Certify any measurement, and so sign for every address of every policy that uses its scheme | Affect policies that use another scheme |
| TEE hardware vendor | Expose program keys and secrets through a hardware flaw, until the flaw is fixed | Change which measurements a policy approves |
| Policy owner | Approve any measurement, or raise the key lifetime cap, after the delay, and so sign for the policy's addresses | Approve a measurement or raise the cap before the delay ends, or affect other policies |
| Policy guardian | Pause the policy, remove measurements and anchors, revoke all program keys, and cancel pending proposals | Approve a measurement or trust anchor |
| Signer program (approved code) | Sign for any user tag of its policy, as its code allows. Choose its rotation interval and key lifetime within the cap | Run with a different measurement under the same registration, or keep a key past the cap |
| Identity method's parties, such as an OpenID Connect provider, the certificate authorities the program trusts for its `jwks_uri` host, or the program's email account | Prove any identity of that method, and so bind a device as any of its users | Sign without the operator's approval, or affect other methods |
| Wallet operator | Approve or refuse each signature, revoke devices at once, log every approval, and serve the client's code. Because it serves that code, it can read sign-in proofs or swap device keys for users who sign in while it is compromised | Sign without a device proof from a bound device, except for users who sign in while it is compromised, or register a key for unapproved code |
| User's device | Sign device proofs, and transactions within the limits of its access key, if it has one | Sign an owner operation, or exceed its access key's limits, without the operator's approval |
| Validators | Nothing beyond their existing role | |

The [Threat Model](#threat-model) traces what happens when each party is compromised.

### Scope

- Identity methods, device bindings, and operator approvals are rules of the signer program, not of the protocol. Adding a method is a new measurement, not a hardfork. The chain cannot check identity methods, device bindings, or approvals. It checks only that approved code signed.
- A policy cannot express an approval that depends on claims other than the measurement, such as a cloud project. The signer program enforces those conditions itself.
- Attested signatures sign transactions, key authorizations, and configurable-account owner approvals. Contracts verify them for any hash through TIP-1020, against the current registry state.
- Access keys are optional. The device key can be the account's access key, with the limits the app requests ([Access keys](#access-keys)).
- Each identity is a separate attested signer. A configurable account with several attested signers as owners links one person's methods.
- Each policy uses one scheme. Combining schemes uses configurable-account thresholds.
- Attested signer addresses do not depend on the measurement, so upgrades of the signer program keep every address.
- Program keys are not portable between chains, because the binding names the chain ID and the registry.
- Moving an existing account to an attested signer requires a configurable account that adds the attested signer as an owner.

### Components

Read the components in this order. Each one opens with its role in the lifecycle above and links back here for shared context.

| TIP | What it adds | Why read it here |
|---|---|---|
| [TIP-1160: Attested Signatures](./tip-1160.md) | Signature type `0x06`, address derivation, validity rules, accepted contexts, and gas | Defines what a transaction carries and how the node checks it (lifecycle step 5). The other components produce the registry state that this check reads |
| [TIP-1161: Attested Key Registry](./tip-1161.md) | Policies, delays, the guardian, key slots, the key lifetime cap, `registerKey`, and the scheme interface | Defines where program keys and approved measurements come from (lifecycle steps 1 and 2) |
| [TIP-1162: AWS Nitro Enclaves Attestation](./tip-1162.md) | Scheme `0x01` | First scheme, reusing the Zone verifier's Nitro attestation code. It implements the scheme interface of TIP-1161 |
| [TIP-1163: Google Cloud Confidential Space Attestation](./tip-1163.md) | Scheme `0x02` | Second scheme, using RS256 tokens and policy trust anchors. It implements the same interface |

The signature and registry TIPs activate in the same hardfork as at least one scheme. Later schemes activate independently.

## Signer program requirements

These requirements are not consensus rules. They determine whether a policy's addresses are safe, so wallets SHOULD show users only addresses of policies whose signer program meets them. The program's authorization rules are part of its measured code, so reviewers check them through the measurement.

### General

- **No operator configuration.** The program MUST NOT read security-relevant configuration from its operator, such as the email account, the operator's approval key, or the KMS key. All of it is part of the measured code. Identity provider keys are not configuration: the program fetches them itself from a source fixed in its code ([Provider keys](#provider-keys)). An operator can run approved code anywhere, so its behavior must not depend on where it runs.
- **Secrets.** `k` (the key of the [user tag](#identity-methods)) and the device binding key (which seals bindings in the [reference profile](#reference-authorization-profile)) are secrets that a KMS releases only to TEEs with an approved measurement. The release policy MUST cover the old and new measurements during an upgrade. Losing `k` prevents users from signing in again.
- **Program keys.** The program SHOULD generate a new program key for each registration and alternate between two key slots, so its newest key is current while it replaces the older one. It SHOULD request a lifetime of at least two rotation intervals, and register at least 120 seconds before its newest key expires. It SHOULD write each slot at most once every 360 seconds (`MAX_ATTESTATION_AGE` plus `MAX_FUTURE_SKEW`), so no earlier document for that slot can still register ([TIP-1161](./tip-1161.md#threat-model)). Each running program uses its own pair of slots.
- **Approval log.** The operator SHOULD log every approval it issues, with the digest, the device key, and the time, and keep the log for the [unapproved-signature monitor](#observability).
- **Transport.** The program SHOULD accept requests only from the wallet operator, over mutual TLS (mTLS). The operator serves the client's code, so a compromised operator can read sign-in proofs or swap device keys for users who sign in during the compromise. A direct path from the client to the program helps only for clients whose code the operator does not serve.
- **Builds.** The program MUST build reproducibly from published source. Before the owner proposes a measurement, at least two independent parties SHOULD rebuild it and confirm the measurement. During the delay, monitors SHOULD rebuild every proposed measurement from its published commit.

### Identity methods

Each identity method that a program supports MUST meet these rules:

| Rule | Reason |
|---|---|
| The program verifies the proof itself, with every trust input fixed in its measured code, such as the provider's issuer and key source, the email account, or the normalization, or fetched from a fixed source over TLS that ends in the program | The operator cannot replace the provider or forge a proof |
| The proof commits to the device key, such as an OpenID Connect `nonce` or a challenge that names it | A proof that the operator observes cannot bind another device |
| The identity is `method || ":" || normalized_identifier`, and the method names and normalization rules never change for a policy | The identity determines the address |
| `user_tag = HMAC-SHA256(k, "tempo:user-tag:v1" || identity)` | The chain sees no identifier, and nobody without `k` can link an identifier to its address |

Examples:

| Method | Proof the program verifies |
|---|---|
| Email code | A code that the program sent from its own email account |
| OpenID Connect | An ID token from a fixed issuer, signed by a key that the program fetched from the issuer's fixed `jwks_uri` ([Provider keys](#provider-keys)), whose `nonce` commits to the device key |
| Wallet signature | A signature over a challenge that names the device key |

A passkey usually needs no signer program. It is a native Tempo signer and can own an account directly.

### Provider keys

OpenID Connect providers rotate their signing keys without notice to relying parties. If the keys were fixed in measured code, every rotation would be a new measurement, which waits for the policy's delay. The program instead fixes where the keys come from, and fetches the keys at runtime.

For each OpenID Connect method, the measured code fixes:

| Value | Example |
|---|---|
| `iss` | `https://accounts.google.com` |
| `jwks_uri` | `https://www.googleapis.com/oauth2/v3/certs` |
| Accepted `alg` values | `RS256` |
| Root certificates trusted for the `jwks_uri` host | The provider's own certificate authorities |

The program MUST fetch `jwks_uri` over TLS 1.3 that ends inside the TEE, validate the server's certificate chain against the fixed roots only, using its own clock, and accept the response only from the fixed host. The operator relays the encrypted bytes and cannot read, change, or replay them. The program MUST NOT use discovery (`/.well-known/openid-configuration`) to choose `jwks_uri`, which would add a second fetched input to trust.

| Rule | Reason |
|---|---|
| The program caches a fetched key set for the response's `Cache-Control` `max-age`, and at most `MAX_PROVIDER_KEY_AGE` (24 hours). After that, it treats the set as absent | A key that the provider withdraws stops verifying at the next fetch, and at the latest after `MAX_PROVIDER_KEY_AGE`, even if the operator blocks fetches |
| A token whose `kid` is not in the cache triggers one refetch. The program refetches at most once every 60 seconds for each issuer | A new provider key works at once, and unknown `kid` values cannot amplify load on the provider |
| A token verifies only against an unexpired cached key with a matching `kid` and `alg` | The operator cannot supply a key |
| If a fetch fails and no unexpired set exists, the program issues no binding | A failed fetch stops sign-ins and never weakens verification |

Pinning the provider's own certificate authorities, rather than a general WebPKI bundle, means that a provider changing certificate authorities without notice stops sign-ins through that method until new code is approved. A general bundle would instead let any public certificate authority's misissued certificate serve keys.

### Reference authorization profile

A policy MAY approve code with other authorization rules. This profile is the reference.

- **Device binding.** At sign-in, the program issues the binding as a token that it seals with the device binding key. The token covers the user tag, the device key, the chain, the policy, and an expiry of at most 30 days. Only a verified sign-in issues one. The program keeps no list of bindings, so it cannot revoke one before its expiry.
- **Device revocation.** The operator revokes a device by refusing to approve its operations, at once and for every later signature. A user who signs out asks the operator to do this.
- **Device proof.** The device key signs `keccak256("tempo:attested-device" || chainId || policyId || deviceKeyId || digest || nonce || timestamp)`. The binding already ties the device key to one user tag, so the proof does not name it. The program accepts a timestamp within 60 seconds of its clock and rejects a nonce it saw in that window.
- **Operator approval.** The operator's approval key signs `keccak256("tempo:attested-approval" || chainId || policyId || deviceKeyId || digest || nonce || expiry)`, with an expiry of at most 120 seconds. The approval key's public key is fixed in the measured code or in the policy.
- **Signing.** The program signs a digest only if the binding, the device proof, and the approval are valid and name the same device key, digest, and nonce. It signs as the binding's user tag.
- **Sign-in signature.** Within 60 seconds of the sign-in that issued a binding, the program signs one digest with that binding and the operator's approval, without a device proof. The verified sign-in authorizes this signature, so a sign-in and its first operation, such as a key authorization for the device key, take one request. The program accepts it once for each binding.
- **Rate limits.** The program limits sign-ins for each identity and in total, and signatures for each device.

## Costs

Dollar amounts use the [TIP-1067](./tip-1067.md) base-fee cap of 1.2 × 10^10 attodollars per gas.

| Operation | Gas | Cost at the cap | Paid |
|---|---|---|---|
| Rotating a Nitro program key with a five-certificate chain, into a slot that holds a key | About 900,000: 790,000 for verification ([TIP-1162](./tip-1162.md#gas)), and about 110,000 for the transaction base, the slot write, the event, and calldata | about $0.011, of which $0.0095 is verification | Each rotation, at the interval the operator chooses |
| First registration into an empty slot | About 250,000 more than a rotation, for [TIP-1000](./tip-1000.md)'s new-slot cost | about $0.003 more | Once for each slot a policy uses |
| Registering a Confidential Space program key | To be measured ([TIP-1163](./tip-1163.md#gas)) | | Each rotation |
| Verifying an attested signature | P256 verification plus 4,200 ([TIP-1160](./tip-1160.md#gas)) | about $0.00015 | Each operation that the attested signer signs |
| Transactions signed by an access key | No extra gas | | Where the user authorized an access key |

The part outside verification is measured on a local node under the dev scheme, with a three-certificate chain (490,000 gas of verification) and about 2,340 bytes of calldata:

| Registration | Gas used | Outside verification |
|---|---|---|
| First write, into an empty slot | 811,949 | 321,949 |
| Rotation, into a slot that holds a key | 562,625 | 72,625 |

The difference, 249,324 gas, is the new-slot cost that a rotation avoids. A production document is about 2,200 bytes longer, which adds about 35,000 gas of calldata.

At one rotation every hour, one Nitro signer program costs about $0.26 per day at the cap, of which $0.23 is verification.

## Interfaces

This TIP adds no interface of its own. [TIP-1161](./tip-1161.md#interfaces) defines the registry interface. The signature type and the attestation schemes add no contract interface.

# Compatibility

No existing transaction, account, or signature changes. Before activation, type `0x06` is rejected in every context ([TIP-1160](./tip-1160.md#activation)).

| Integration | What changes |
|---|---|
| Wallets and SDKs, such as ox and Viem | Need attested signer address derivation, the `0x06` encoding, and builders for transactions, key authorizations, and owner approvals with an attested signature ([TIP-1160](./tip-1160.md)) |
| Wallet clients | Generate a device key at each sign-in, keep it on the device, and sign device proofs for each operation |
| Wallet operators | Run a signer program in a TEE, register its program keys, operate a policy with an owner and guardian ([TIP-1161](./tip-1161.md)), and approve each signature |
| Explorers and indexers | Decode type `0x06` signatures and registry events. An attested signer is an ordinary account with no code |
| Contracts using TIP-1020 | `recover` and `verify` accept attested signatures checked against the current registry state ([TIP-1160](./tip-1160.md#tip-1020-verification)) |
| RPC simulation and gas estimation | Apply the same registry reads and charges as block validation, and report which check failed ([TIP-1160](./tip-1160.md#compatibility)) |
| Existing accounts | Unchanged. [Scope](#scope) covers moving one to an attested signer, which needs a configurable account, where active |

# Observability

The registry emits events for policy changes and registrations ([TIP-1161](./tip-1161.md#observability)). Each answers an operational question:

| Question | Evidence |
|---|---|
| Which code can sign for a policy's addresses now, and which code will be able to after the delay? | `MeasurementProposed`, `MeasurementRemoved`, and `ProposalCancelled` |
| Which code signed a given operation? | `KeyRegistered` names the measurement of each program key, and the attested signature names the program key |
| Did an incident response take effect? | `KeysRevoked`, `PolicyPaused`, and `MeasurementRemoved` |
| Is a signer program healthy? | A `KeyRegistered` event for each of its slots before that slot's key expires |
| Did a program key sign without the operator's approval? | Every attested signature onchain, compared with the operator's approval log |

Monitors SHOULD alert on every `MeasurementProposed` whose measurement no independent rebuild reproduces, on every `MaxKeyLifetimeChanged`, and on a policy with no current program key.

Every attested signature is public onchain, with its policy, slot, program key, and the digest it signs. The operator logs every approval it issues. A monitor SHOULD compare each attested signature of the policy in each block with the log. A signature with no matching approval means that a program key leaked or that the signer program's authorization was bypassed. The guardian then pauses the policy or revokes its keys. The monitor detects a leaked key on its first onchain use, so the key lifetime is a secondary defense, and operators can rotate rarely. Signatures that never reach the chain, such as a message signature checked offchain, are invisible to the monitor. Wallet operators SHOULD log approvals and device revocations. [TIP-1160](./tip-1160.md#observability) recommends that nodes export attested-signature rejections by reason.

# Threat Model

The most consequential attack is signing for every address of a policy. The attacker must make the registry accept a program key held by code it controls. The registry accepts a key only from a document that the scheme verifies and whose measurement the policy approved before the delay ended.

| Actor | Capability and trust | Boundary check and failure consequence |
|---|---|---|
| Compromised wallet operator, insider, or deploy pipeline | Controls servers, deploys, the approval key, and the TEE host | Different code has a different measurement, so registration fails ([TIP-1161](./tip-1161.md#registration)). The approval key signs nothing without a device proof, and only a verified sign-in binds a device. It can read sign-in proofs or swap device keys for users who sign in during the compromise, because it serves the client's code |
| Stolen device key | Signs device proofs, and transactions as its access key, if it has one | Spends within its access key's limits until the access key expires or is revoked onchain. Gets no owner signature without the operator's approval, and the operator refuses at once |
| Identity method's parties | Prove any identity of the method | Can bind a device as any user of that method. Signing still needs the operator's approval. For email, whoever reads the program's outbound messages can do this, so the email account needs few admins, no message previews, and alerts on changes |
| User's mailbox or provider account | Proves that user's identity | Binds a device as that user only |
| Operator blocking provider key fetches | Drops or delays the program's TLS connections to a `jwks_uri` | Sign-ins through that method fail once the cache expires, which denies service. Cannot inject keys, because TLS ends in the program and its certificate is checked against fixed roots. Keeps a withdrawn provider key usable for at most `MAX_PROVIDER_KEY_AGE` ([Provider keys](#provider-keys)) |
| Misissued TLS certificate for a `jwks_uri` host | Serves a forged key set to the program | Binds a device as any user of that method. Signing still needs the operator's approval. Pinning the provider's own certificate authorities narrows this to them |
| Compromised build pipeline | Produces a malicious image from reviewed source | The image's measurement differs from independent rebuilds. Monitors flag the proposal, and the guardian cancels it during the delay. Without independent rebuilds, the owner approves it and it can sign as every user |
| Compromised policy owner | Proposes any measurement or trust anchor, and raises the key lifetime cap | Proposals and cap increases wait for the delay. The guardian cancels them and can pause the policy. If no one acts, the malicious code signs as every user after the delay |
| Compromised guardian | Pauses the policy and removes entries | Can deny service. Cannot approve code |
| Attestation service | Signs documents | Fully trusted for its scheme. A compromise lets the attacker register keys for any measurement. Guardians remove the scheme's trust anchors where it has them, or pause, which stops new registrations at once. Nitro has no anchors, so its guardians pause. A hardfork disables the scheme |
| TEE hardware flaw | Reads program memory | Exposes the current program keys, `k`, and the device binding key. The [unapproved-signature monitor](#observability) detects the first onchain use of a leaked program key, and the guardian pauses or revokes keys at once. Without a response, the key signs until a rotation overwrites its slot or its registration expires, at most the key lifetime cap. A leaked `k` links identities to addresses. A leaked binding key lets an attacker bind devices, which still need the operator's approval |
| Leaked program key | Signs for any user tag of the policy | Every use is public onchain. The monitor finds a signature with no matching approval, and the guardian pauses or revokes. The key lifetime cap bounds the exposure if no one responds, and the owner raises the cap only after the delay |

Residual risks:

- An attacker that controls both the wallet operator and an identity method, or both the operator and a user's device, can sign as the affected users.
- Users who sign in while the operator's client code is compromised can have their device key replaced.
- The design trusts the attestation service and the TEE vendor for each scheme. A configurable account that requires owners under two schemes removes the single vendor, at the cost of two signatures per operation.
- Losing `k` stops new sign-ins for every user of the policy.
- A leaked program key signs until the guardian responds to the monitor, or until its registration expires. A policy without a monitor relies on the key lifetime cap alone.

# Invariants

- An attested signer's address depends only on its policy ID and user tag.
- A signature is valid only if its program key was registered with a document whose measurement was active in the policy at registration.
- A measurement proposal never becomes active before its activation time, which is at least the policy's delay after the proposal.
- Removing a measurement, revoking keys, or pausing a policy takes effect for every later transaction in the same block.
- A program key is valid only while it occupies its key slot and is unexpired. It is never valid longer than the policy's key lifetime cap after its registration.
- Code that follows the reference profile signs a digest only with a valid device binding, device proof, and operator approval for that digest and nonce. Reviewers check this property in the measured code. Consensus cannot.

## Test Cases

- A full sign-in and a signed transaction under each scheme, for each identity method the reference program supports.
- A device proof without an approval, an approval without a device proof, and both for different digests or nonces.
- A sign-in proof that commits to another device key.
- A measurement proposal that the guardian cancels before activation, and one that activates exactly at its activation time.
- A program key used one second after expiry, after a rotation overwrites its slot, after `revokeKeys`, and after `pause` earlier in the same block.
- Two slots whose keys are current together during a rotation.
- A signer program upgrade that keeps every address.
