From 4d3f669e0c689782459c5aa7ebf7469e936daad2 Mon Sep 17 00:00:00 2001 From: howy <132113803+howydev@users.noreply.github.com> Date: Wed, 18 Feb 2026 19:54:43 -0500 Subject: [PATCH 1/4] feat: update and tighten tip1020 spec --- tips/tip-1020.md | 84 ++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 50 deletions(-) diff --git a/tips/tip-1020.md b/tips/tip-1020.md index d35d7cb9c9..a679b00774 100644 --- a/tips/tip-1020.md +++ b/tips/tip-1020.md @@ -2,7 +2,7 @@ id: TIP-1020 title: Signature Verification Precompile description: A precompile for verifying Tempo signatures onchain. -authors: Jake Moxey (@jxom) +authors: Jake Moxey (@jxom), Tanishk Goyal (@legion2002), Howy (@howydev) status: Draft related: Tempo Transaction Spec protocolVersion: T2 @@ -18,7 +18,7 @@ This TIP introduces a signature verification precompile that enables contracts t Tempo supports multiple signature schemes beyond standard secp256k1. Currently, contracts cannot verify Tempo signatures onchain without implementing custom verification logic for each signature type. -This precompile exposes Tempo's native signature verification to contracts, reusing the existing audited verification logic from Tempo transaction processing. +On top of that, since smart contracts have to statically bind their verification logic at deployment time, they cannot natively support future Tempo account signature schemes introduced after deployment. This precompile serves as a stable interface that smart contracts can use to maintain forward compatibility with future Tempo account types and signature schemes. ## Specification @@ -51,8 +51,9 @@ interface ISignatureVerifier { /// @param signer The expected signer address /// @param hash The message hash that was signed /// @param signature The encoded signature (see Tempo Transaction spec for formats) - /// @return True if valid, reverts otherwise - function verifyStateful(address signer, bytes32 hash, bytes calldata signature) external view returns (bool); + /// @return result True if valid, reverts otherwise + /// @return keyId The keyId of the access key if it was used, otherwise `address(0)` + function verifyStateful(address signer, bytes32 hash, bytes calldata signature) external view returns (bool result, address keyId); } ``` @@ -75,17 +76,17 @@ The precompile MUST use the same verification logic as Tempo transaction signatu #### `verify` -The `verify` function MUST support only stateless signature types (secp256k1, P256, WebAuthn). It MUST revert if a Keychain signature (type prefix `0x03`) is provided. +The `verify` function MUST support only stateless signature types (secp256k1, P256, WebAuthn). If a Keychain signature (type prefix `0x03`) is used, it MUST revert. #### `verifyStateful` For stateless signature types (secp256k1, P256, WebAuthn), `verifyStateful` MUST behave identically to `verify`. -For Keychain signatures (type prefix `0x03`), `verifyStateful` MUST perform the following steps: +For Keychain signatures (type prefix `0x03`), `verifyStateful` performs the following steps: 1. **Decode the Keychain signature** — strip the `0x03` prefix, extract the `user_address` (20 bytes) and the `inner_signature` (remaining bytes). 2. **Verify the `signer` matches** — the `signer` parameter MUST equal the `user_address` extracted from the Keychain signature. -3. **Derive the access key address** from the inner signature (the signer of the inner signature is the access key). +3. **Derive the access key address** from the inner signature (the access key signs the inner signature). 4. **Verify the inner signature** — validate that the inner signature is cryptographically valid for the given `hash`. 5. **Validate the access key is authorized** — query the [AccountKeychain precompile](https://docs.tempo.xyz/protocol/transactions/AccountKeychain) (`0xaAAAaaAA00000000000000000000000000000000`) and check that: - The key exists (i.e., `keyInfo.keyId != address(0)`) @@ -94,16 +95,20 @@ For Keychain signatures (type prefix `0x03`), `verifyStateful` MUST perform the ### Gas Costs -Gas costs follow the [Tempo Transaction Signature Gas Schedule](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-verification-gas-schedule): +Gas costs vary based on the signature being processed. The precompile first checks which signature verification is being used. We check `length == 65 bytes` to detrermine if the signature is ECDSA, otherwise the typeId is used to differentiate between the other signature types. After determining the signature type, the precompile MUST revert if the gas passed to the call is insufficient based on the signature type. The gas costs per signature is detailed below: | Type | Gas Cost | |------|----------| | secp256k1 | 3,000 | | P256 | 8,000 | | WebAuthn | 8,000 | -| Keychain | inner signature cost + 2,600 (cold `CALL` to AccountKeychain) | +| Keychain | Inner signature cost + State access cost for access key info | + +For the keychain use case, the signature verification precompile MUST parse `inner_signature` and recursively apply the same filtering rules to determine the signature type and it's associated cost. Then, it MUST ensure that the call has sufficient gas before performing signature verification. + +For the keychain case, the inner signature cost is paid first, then the state access cost. EVM rules apply for the state access - if the cold access cost is paid during this operation, the slot in the AccountKeychain precompile in the `keys` map for that `signer` and `keyId` is treated as warm for subsequent storage operations. -**Note**: Unlike transaction signature verification, WebAuthn signatures do not incur additional calldata gas in the precompile. The EVM already charges calldata gas when the signature is passed as a function argument, so adding it again would result in double-charging. +**Note**: The gas costs are in-line with [Tempo transactions](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-verification-gas-schedule). WebAuthn signatures do not incur additional calldata gas in the precompile since the caller to the precompile will pay for this additional size within the EVM, either via calldata or memory expansion costs. ## Backward Compatibility @@ -125,6 +130,15 @@ Solidity developers commonly use `ecrecover(hash, v, r, s)` to recover an addres For backwards compatibility, secp256k1 signatures are encoded as **65 bytes `r || s || v` with no type prefix**. Callers who already produce 65-byte secp256k1 signatures can reuse them directly as the `signature` argument to this precompile. +## Forward Compatibility + +It is expected that this precompile will be updated when other account types are introduced to maintain forward compatibility with Tempo accounts and Tempo access keys. + +## Security Considerations + +1. **Access Key Statefulness**: Developers should be mindful that the verification result is stateful and could change if an access key is revoked. +2. **Access Key Scope**: `verifyStateful` confirms that an access key is authorized for an account (exists, not revoked, not expired), but does **not** enforce key-level permissions. Access keys may carry restricted scopes. Dapps that rely on `verifyStateful` SHOULD independently verify that the access key's permissions are sufficient for the requested operation, to avoid granting privileges the user did not intend to delegate. +3. **Access Key Replay Across Accounts** A single access key MAY be authorized on multiple accounts. Because `verifyStateful` accepts the `signer` (account) as a parameter and validates the keychain binding (invariant SV6), application-layer signatures (e.g., EIP-712 permits) verified via `verifyStateful` SHOULD include the account address in the signed digest. Without this, a valid access key signature produced for one account could be replayed against another account that has authorized the same key. ## Invariants @@ -132,43 +146,13 @@ For backwards compatibility, secp256k1 signatures are encoded as **65 bytes `r | |----|-----------|-------------| | **SV1** | Transaction-equivalent verification | For any signature type supported by a given function, the precompile MUST use the same cryptographic verification rules as Tempo transaction signature validation. | | **SV2** | Stateless/stateful equivalence | For stateless signature types (secp256k1, P256, WebAuthn), `verifyStateful(signer, hash, signature)` MUST behave identically to `verify(signer, hash, signature)` — same acceptance/rejection, same gas, same revert behavior. | -| **SV3** | Keychain rejected by `verify` | `verify` MUST revert when the signature has a Keychain prefix (`0x03`). It MUST NOT return `false` or silently ignore the type. This prevents accidental omission of key authorization checks. | -| **SV4** | Signer match required | For any accepted signature, the recovered signer address MUST equal the `signer` argument. A mismatch MUST revert. | -| **SV5** | Deterministic input binding | The precompile MUST verify the signature over the exact `hash` parameter supplied. It MUST NOT apply additional hashing, domain separation, or message prefixing beyond what the corresponding signature scheme specifies. | -| **SV6** | Keychain outer binding | For Keychain signatures, `verifyStateful` MUST enforce that `signer` equals the `user_address` extracted from the Keychain signature wrapper. | -| **SV7** | Keychain inner signature validity | For Keychain signatures, `verifyStateful` MUST verify the inner (access-key) signature cryptographically for the provided `hash` before consulting authorization state. | -| **SV8** | Keychain authorization required | For Keychain signatures, `verifyStateful` MUST query the AccountKeychain precompile and require: (a) key exists (`keyId != address(0)`), (b) key is not revoked, (c) key has not expired. Any failure MUST revert. | -| **SV9** | Keychain recursion prevention | The inner signature of a Keychain signature MUST NOT itself be a Keychain signature. Nested Keychain signatures MUST be rejected. | -| **SV10** | P256 malleability resistance | P256 signatures MUST satisfy the low-s requirement (`s <= n/2`). Signatures with high-s values MUST be rejected. | -| **SV11** | WebAuthn bounds enforcement | WebAuthn signature parsing MUST enforce the length bounds and structural validity checks used by Tempo transaction verification, preventing out-of-bounds reads and pathological resource usage. | -| **SV12** | No state mutation | Both functions MUST NOT write EVM state, emit logs, or mutate persistent or transient storage. Keychain support is implemented via read-only queries to the AccountKeychain precompile. | -| **SV13** | Revert on failure | On any invalid signature, invalid encoding, unsupported type, signer mismatch, or Keychain authorization failure, the precompile MUST revert. It MUST NOT return `false`. | -| **SV14** | Gas schedule consistency | Gas charged MUST follow the Tempo Transaction Signature Gas Schedule. WebAuthn MUST NOT double-charge for calldata beyond the EVM's normal calldata gas. | -| **SV15** | Signature type disambiguation | Exactly 65 bytes MUST be interpreted as secp256k1 (no prefix). Any non-65-byte signature MUST be interpreted using the leading type byte. Unknown type identifiers MUST revert. | - -## Rationale - -### Reusing Tempo Transaction Logic - -This precompile hooks directly into the existing signature verification logic used by Tempo transactions. This guarantees: - -1. **1:1 compatibility** with transaction signature verification -2. **Audited code** — no new cryptographic implementations -3. **Consistent gas schedule** — same costs as transaction signatures - -### Signer Parameter - -The `signer` parameter is required because: - -- For secp256k1, callers typically want to verify a specific expected signer -- For P256/WebAuthn, the derived address must be compared against an expected signer - -This allows a single function call to both verify the signature and confirm the signer. - -### Revert on Invalid - -The function reverts on invalid signatures rather than returning `false`: - -- **Explicit errors**: Callers know exactly why verification failed -- **Fail-fast**: Invalid signatures are programming errors or attacks -- **Simple success path**: If the call doesn't revert, the signature is valid +| **SV3** | Keychain rejected by `verify` | `verify` MUST revert for keychain type signatures. | +| **SV4** | Keychain outer binding | For Keychain signatures, `verifyStateful` MUST enforce that `signer` equals the `user_address` extracted from the Keychain signature wrapper. | +| **SV5** | Keychain inner signature validity | For Keychain signatures, `verifyStateful` MUST verify the inner (access-key) signature cryptographically for the provided `hash` before consulting authorization state. | +| **SV6** | Keychain authorization required | For Keychain signatures, `verifyStateful` MUST query the AccountKeychain precompile and require: (a) key exists (`keyId != address(0)`), (b) key is not revoked, (c) key has not expired. Any failure MUST revert. | +| **SV7** | Keychain recursion prevention | The inner signature of a Keychain signature MUST NOT itself be a Keychain signature. Nested Keychain signatures MUST be rejected. | +| **SV8** | P256 and ECDSA signature malleability resistance | P256 and ECDSA signatures MUST satisfy the low-s requirement (`s <= n/2`). Signatures with high-s values MUST be rejected. | +| **SV9** | WebAuthn bounds enforcement | WebAuthn signature parsing MUST enforce the length bounds and structural validity checks used by Tempo transaction verification, preventing out-of-bounds reads and pathological resource usage. | +| **SV10** | Revert on failure | On any invalid signature, invalid encoding, unsupported type, signer mismatch, or Keychain authorization failure, the precompile MUST revert. | +| **SV11** | Gas schedule consistency | Gas charged MUST follow the Tempo Transaction Signature Gas Schedule. | +| **SV12** | Signature type disambiguation | Exactly 65 bytes MUST be interpreted as secp256k1 (no prefix). Any non-65-byte signature MUST be interpreted using the leading type byte. Unknown type identifiers MUST revert. | \ No newline at end of file From 0e9092ce9a4ff7e768b74f3731bdb7d43d7c7d01 Mon Sep 17 00:00:00 2001 From: howy <132113803+howydev@users.noreply.github.com> Date: Wed, 18 Feb 2026 19:57:49 -0500 Subject: [PATCH 2/4] . --- tips/tip-1020.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tips/tip-1020.md b/tips/tip-1020.md index a679b00774..00ce14fd8a 100644 --- a/tips/tip-1020.md +++ b/tips/tip-1020.md @@ -76,13 +76,13 @@ The precompile MUST use the same verification logic as Tempo transaction signatu #### `verify` -The `verify` function MUST support only stateless signature types (secp256k1, P256, WebAuthn). If a Keychain signature (type prefix `0x03`) is used, it MUST revert. +The `verify` function MUST support only stateless signature types (secp256k1, P256, WebAuthn). It MUST revert if a Keychain signature (type prefix `0x03`) is provided. #### `verifyStateful` For stateless signature types (secp256k1, P256, WebAuthn), `verifyStateful` MUST behave identically to `verify`. -For Keychain signatures (type prefix `0x03`), `verifyStateful` performs the following steps: +For Keychain signatures (type prefix `0x03`), `verifyStateful` MUST perform the following steps: 1. **Decode the Keychain signature** — strip the `0x03` prefix, extract the `user_address` (20 bytes) and the `inner_signature` (remaining bytes). 2. **Verify the `signer` matches** — the `signer` parameter MUST equal the `user_address` extracted from the Keychain signature. From bf54073361fb7d77e138871fffd531e04924778a Mon Sep 17 00:00:00 2001 From: howy <132113803+howydev@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:16:01 -0500 Subject: [PATCH 3/4] chore: cleanup --- tips/tip-1020.md | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/tips/tip-1020.md b/tips/tip-1020.md index 00ce14fd8a..9d3cfd7f03 100644 --- a/tips/tip-1020.md +++ b/tips/tip-1020.md @@ -18,7 +18,7 @@ This TIP introduces a signature verification precompile that enables contracts t Tempo supports multiple signature schemes beyond standard secp256k1. Currently, contracts cannot verify Tempo signatures onchain without implementing custom verification logic for each signature type. -On top of that, since smart contracts have to statically bind their verification logic at deployment time, they cannot natively support future Tempo account signature schemes introduced after deployment. This precompile serves as a stable interface that smart contracts can use to maintain forward compatibility with future Tempo account types and signature schemes. +Additionally, since smart contracts have to statically bind their verification logic at deployment time, they cannot natively support future Tempo account signature schemes introduced after deployment. This precompile serves as a stable interface that smart contracts can use to maintain forward compatibility with future Tempo account types and signature schemes. ## Specification @@ -68,17 +68,11 @@ Signatures MUST be encoded using the same format as [Tempo Transaction signature | WebAuthn | `0x02 \|\| webauthn_data \|\| r \|\| s \|\| x \|\| y` | 129–2049 bytes | | Keychain | `0x03 \|\| user_address \|\| inner_signature` | variable | -For backwards compatibility, secp256k1 signatures have no type prefix. All other signature types are prefixed with a type identifier byte. - ### Verification Logic The precompile MUST use the same verification logic as Tempo transaction signature validation. See the [Tempo Transaction Signature Validation spec](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-validation) for details. -#### `verify` - -The `verify` function MUST support only stateless signature types (secp256k1, P256, WebAuthn). It MUST revert if a Keychain signature (type prefix `0x03`) is provided. - -#### `verifyStateful` +#### `verifyStateful` Implementation For stateless signature types (secp256k1, P256, WebAuthn), `verifyStateful` MUST behave identically to `verify`. @@ -95,7 +89,7 @@ For Keychain signatures (type prefix `0x03`), `verifyStateful` MUST perform the ### Gas Costs -Gas costs vary based on the signature being processed. The precompile first checks which signature verification is being used. We check `length == 65 bytes` to detrermine if the signature is ECDSA, otherwise the typeId is used to differentiate between the other signature types. After determining the signature type, the precompile MUST revert if the gas passed to the call is insufficient based on the signature type. The gas costs per signature is detailed below: +The precompile MUST charge gas and verify sufficient gas is available **before** performing any cryptographic verification. The precompile MUST revert with out-of-gas if the call has insufficient gas for the signature type. Gas costs per signature type: | Type | Gas Cost | |------|----------| @@ -104,33 +98,32 @@ Gas costs vary based on the signature being processed. The precompile first chec | WebAuthn | 8,000 | | Keychain | Inner signature cost + State access cost for access key info | -For the keychain use case, the signature verification precompile MUST parse `inner_signature` and recursively apply the same filtering rules to determine the signature type and it's associated cost. Then, it MUST ensure that the call has sufficient gas before performing signature verification. +For Keychain signatures, the precompile MUST parse `inner_signature` to determine its type and associated cost. The inner signature cost is paid first, then the state access cost for the accessed slot in the AccountKeychain precompile in the `keys` map for that `signer` and `keyId`. Standard EVM warm/cold rules apply for this state access. -For the keychain case, the inner signature cost is paid first, then the state access cost. EVM rules apply for the state access - if the cold access cost is paid during this operation, the slot in the AccountKeychain precompile in the `keys` map for that `signer` and `keyId` is treated as warm for subsequent storage operations. +**Note**: Gas costs are in-line with the [Tempo Transaction Signature Gas Schedule](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-verification-gas-schedule). WebAuthn signatures do not incur additional calldata gas in the precompile since the caller pays for the additional size within the EVM via calldata or memory expansion costs. -**Note**: The gas costs are in-line with [Tempo transactions](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-verification-gas-schedule). WebAuthn signatures do not incur additional calldata gas in the precompile since the caller to the precompile will pay for this additional size within the EVM, either via calldata or memory expansion costs. - -## Backward Compatibility +## Compatibility This TIP is **additive**. It introduces a new precompile at `0x5165300000000000000000000000000000000000` and does **not** modify existing EVM opcodes, transaction formats, or any existing Ethereum precompiles. -### Interaction with Ethereum `ecrecover` (`0x01`) +### Backward Compatibility + +#### Ethereum `ecrecover` (`0x01`) -`ecrecover` continues to function unchanged. This TIP does not replace, intercept, or alter the gas schedule of any existing Ethereum precompile. The signature verification precompile exists to verify **Tempo-native multi-scheme signatures** (secp256k1, P256, WebAuthn, Keychain) using the same logic as Tempo transaction processing. `ecrecover` remains the standard tool for Ethereum-style secp256k1 address recovery. +This TIP does not modify `ecrecover` or any existing Ethereum precompile. `ecrecover` remains the standard tool for Ethereum-style secp256k1 address recovery. -### Developer-Facing Differences vs. `ecrecover` +#### Developer-Facing Differences vs. `ecrecover` Solidity developers commonly use `ecrecover(hash, v, r, s)` to recover an address and then compare it to an expected signer. The Tempo signature verification precompile differs in several intentional ways: 1. **Explicit signer parameter**: `ecrecover` returns a recovered address (or `address(0)`), and the caller compares it. TIP-1020 takes an `address signer` and returns success only if the recovered signer matches the provided `signer`. 2. **Failure behavior (revert vs. sentinel)**: `ecrecover` returns `address(0)` on invalid input or failed recovery. TIP-1020 functions **revert** on invalid signatures and return `true` only on success. Contracts that want non-reverting behavior SHOULD wrap calls using `try/catch` (high-level) or `staticcall` (low-level) and treat failure as "invalid signature". -3. **State access**: `ecrecover` is purely cryptographic and can be treated as `pure`. `verify` is also cryptographic-only (stateless). `verifyStateful` is **stateful** for Keychain signatures because it queries the AccountKeychain precompile; callers MUST assume results may change as key state changes over time. -### Compatibility with Existing secp256k1 Signature Payloads +#### Existing secp256k1 Signature Payloads For backwards compatibility, secp256k1 signatures are encoded as **65 bytes `r || s || v` with no type prefix**. Callers who already produce 65-byte secp256k1 signatures can reuse them directly as the `signature` argument to this precompile. -## Forward Compatibility +### Forward Compatibility It is expected that this precompile will be updated when other account types are introduced to maintain forward compatibility with Tempo accounts and Tempo access keys. @@ -138,7 +131,7 @@ It is expected that this precompile will be updated when other account types are 1. **Access Key Statefulness**: Developers should be mindful that the verification result is stateful and could change if an access key is revoked. 2. **Access Key Scope**: `verifyStateful` confirms that an access key is authorized for an account (exists, not revoked, not expired), but does **not** enforce key-level permissions. Access keys may carry restricted scopes. Dapps that rely on `verifyStateful` SHOULD independently verify that the access key's permissions are sufficient for the requested operation, to avoid granting privileges the user did not intend to delegate. -3. **Access Key Replay Across Accounts** A single access key MAY be authorized on multiple accounts. Because `verifyStateful` accepts the `signer` (account) as a parameter and validates the keychain binding (invariant SV6), application-layer signatures (e.g., EIP-712 permits) verified via `verifyStateful` SHOULD include the account address in the signed digest. Without this, a valid access key signature produced for one account could be replayed against another account that has authorized the same key. +3. **Access Key Replay Across Accounts**: A single access key MAY be authorized on multiple accounts. Because `verifyStateful` accepts the `signer` (account) as a parameter and validates the keychain binding (invariant SV6), application-layer signatures (e.g., EIP-712 permits) verified via `verifyStateful` SHOULD include the account address in the signed digest. Without this, a valid access key signature produced for one account could be replayed against another account that has authorized the same key. ## Invariants From a86e8e0ab5057c345b8e6040b3ed4089b65829c2 Mon Sep 17 00:00:00 2001 From: howy <132113803+howydev@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:17:32 -0500 Subject: [PATCH 4/4] Apply suggestion from @howydev --- tips/tip-1020.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tips/tip-1020.md b/tips/tip-1020.md index 9d3cfd7f03..dffb387220 100644 --- a/tips/tip-1020.md +++ b/tips/tip-1020.md @@ -18,7 +18,7 @@ This TIP introduces a signature verification precompile that enables contracts t Tempo supports multiple signature schemes beyond standard secp256k1. Currently, contracts cannot verify Tempo signatures onchain without implementing custom verification logic for each signature type. -Additionally, since smart contracts have to statically bind their verification logic at deployment time, they cannot natively support future Tempo account signature schemes introduced after deployment. This precompile serves as a stable interface that smart contracts can use to maintain forward compatibility with future Tempo account types and signature schemes. +Additionally, since smart contracts have to statically bind their verification logic at deployment time, developers cannot maintain forward compatibility with future Tempo account signature schemes introduced after deployment without making their contracts upgradeable. This precompile serves as a stable interface that smart contracts can use to maintain forward compatibility with future Tempo account types and signature schemes. ## Specification