diff --git a/tips/tip-1020.md b/tips/tip-1020.md index d35d7cb9c9..dffb387220 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. +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 @@ -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); } ``` @@ -67,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`. @@ -85,7 +80,7 @@ For Keychain signatures (type prefix `0x03`), `verifyStateful` MUST perform the 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,37 +89,49 @@ 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): +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 | |------|----------| | 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 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. -**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**: 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. -## 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 -`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. +#### Ethereum `ecrecover` (`0x01`) -### Developer-Facing Differences vs. `ecrecover` +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` 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 + +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 +139,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