From f041188c22ccd0cb2926ae5bbc4472a7060d346b Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Fri, 9 Jan 2026 19:17:06 +0400 Subject: [PATCH 1/2] fix: keychain auth docs --- .../transactions/spec-tempo-transaction.mdx | 62 ++++++++++++------- docs/vocs.config.tsx | 14 +++-- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/docs/pages/protocol/transactions/spec-tempo-transaction.mdx b/docs/pages/protocol/transactions/spec-tempo-transaction.mdx index 7966946857..dcc6cbf909 100644 --- a/docs/pages/protocol/transactions/spec-tempo-transaction.mdx +++ b/docs/pages/protocol/transactions/spec-tempo-transaction.mdx @@ -424,15 +424,19 @@ The transaction is RLP encoded as follows: rlp([to, value, input]) ``` -**Key Authorization Encoding:** +**Signed Key Authorization Encoding:** + +`SignedKeyAuthorization` is encoded as a nested RLP list containing the `KeyAuthorization` followed by the signature: ``` rlp([ - chain_id, - key_type, - key_id, - expiry?, // Optional trailing field (omitted or 0x80 if None) - limits?, // Optional trailing field (omitted or 0x80 if None) - signature // PrimitiveSignature bytes + rlp([ // KeyAuthorization (nested list) + chain_id, + key_type, + key_id, + expiry?, // Optional trailing field (omitted or 0x80 if None) + limits? // Optional trailing field (omitted or 0x80 if None) + ]), + signature // PrimitiveSignature bytes (secp256k1 only for root key signatures) ]) ``` @@ -443,6 +447,8 @@ rlp([ - The `sender_signature` field is the final field and contains the TempoSignature bytes (secp256k1, P256, WebAuthn, or Keychain) - KeyAuthorization uses RLP trailing field semantics for optional `expiry` and `limits` +> **Reference Implementation:** See [`test_backwards_compatibility_key_authorization`](https://github.com/tempoxyz/tempo/blob/main/crates/primitives/src/transaction/tempo_transaction.rs#L1559) for a complete Rust example of RLP encoding/decoding with `SignedKeyAuthorization`. + ### WebAuthn Signature Verification WebAuthn verification follows the [Daimo P256 verifier approach](https://github.com/daimo-eth/p256-verifier/blob/master/src/WebAuthn.sol). @@ -571,13 +577,16 @@ limits = Option> (None = unlimited spending) **Signed Format:** -The signed format (`SignedKeyAuthorization`) includes all fields with the `signature` appended: +The signed format (`SignedKeyAuthorization`) wraps the `KeyAuthorization` in a nested RLP list with the signature: ``` -signed_key_authorization = rlp([chain_id, key_type, key_id, expiry?, limits?, signature]) +signed_key_authorization = rlp([ + rlp([chain_id, key_type, key_id, expiry?, limits?]), // KeyAuthorization (nested) + signature // PrimitiveSignature bytes +]) ``` -The `signature` is a `PrimitiveSignature` (secp256k1, P256, or WebAuthn) signed by the root key. +The `signature` is a `PrimitiveSignature` (secp256k1 only) signed by the root key over the `key_authorization_digest`. Note: `expiry` and `limits` use RLP trailing field semantics - they can be omitted entirely when None. @@ -684,16 +693,17 @@ The protocol tracks and enforces spending limits for TIP20 token transfers: ```typescript // Define key parameters const keyAuth = { + chain_id: 42429, // Tempo chain ID (0 = valid on any chain) key_type: SignatureType.P256, // 1 key_id: keyId, // address derived from public key - expiry: timestamp + 86400, // 24 hours from now (or 0 for never) + expiry: timestamp + 86400, // 24 hours from now (or null for never) limits: [ - { token: USDC_ADDRESS, amount: 1000000000 }, // 1000 USDC (6 decimals) - { token: DAI_ADDRESS, amount: 500000000000000000000 } // 500 DAI (18 decimals) + { token: USDC_ADDRESS, limit: 1000000000 }, // 1000 USDC (6 decimals) + { token: DAI_ADDRESS, limit: 500000000000000000000n } // 500 DAI (18 decimals) ] }; - // Compute digest: keccak256(rlp([key_type, key_id, expiry, limits])) + // Compute digest: keccak256(rlp([chain_id, key_type, key_id, expiry?, limits?])) const authDigest = computeAuthorizationDigest(keyAuth); ``` @@ -706,24 +716,34 @@ The protocol tracks and enforces spending limits for TIP20 token transfers: 4. **Build TempoTransaction** ```typescript const tx = { - chain_id: 1, + chain_id: 42429, // Tempo chain ID nonce: await getNonce(account), nonce_key: 0, calls: [{ to: recipient, value: 0, input: "0x" }], gas_limit: 200000, max_fee_per_gas: 1000000000, max_priority_fee_per_gas: 1000000000, + valid_before: null, // Optional: transaction expiry + valid_after: null, // Optional: transaction validity start + fee_token: null, // Optional: fee token address + fee_payer_signature: null, // Optional: for sponsored transactions + aa_authorization_list: [], // EIP-7702 style authorizations key_authorization: { - key_type: keyAuth.key_type, - expiry: keyAuth.expiry, - limits: keyAuth.limits, - key_id: keyAuth.key_id, - signature: rootSignature // Root Key's signature on authDigest + // SignedKeyAuthorization structure + authorization: { + chain_id: 42429, + key_type: keyAuth.key_type, + key_id: keyAuth.key_id, + expiry: keyAuth.expiry, + limits: keyAuth.limits, + }, + signature: rootSignature // Root Key's signature on authDigest }, - // ... other fields }; ``` + > **Note:** When RLP encoding for `eth_sendRawTransaction`, the `key_authorization` field is encoded as a nested list: `rlp([rlp([chain_id, key_type, key_id, expiry?, limits?]), signature])`. The `key_authorization` comes **after** `aa_authorization_list` in the RLP field order. + 5. **Access Key Signs Transaction** ```typescript // Sign transaction with the NEW Access Key being authorized diff --git a/docs/vocs.config.tsx b/docs/vocs.config.tsx index 86bb3c77e9..bde132e69c 100644 --- a/docs/vocs.config.tsx +++ b/docs/vocs.config.tsx @@ -91,11 +91,15 @@ export default defineConfig({ rootDir: '.', banner: { content: ( -
- Testnet migration: We've launched a new testnet. You'll need to update your RPC configuration and redeploy any contracts. The old testnet will be deprecated on March 8th.{' '} - Learn more → -
- ), +
+ Testnet migration: We've launched a new testnet. You'll + need to update your RPC configuration and redeploy any contracts. The + old testnet will be deprecated on March 8th.{' '} + + Learn more → + +
+ ), dismissable: true, }, socials: [ From 208b515172a9b9a0bd327ce77a2194206937832f Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Fri, 9 Jan 2026 19:24:06 +0400 Subject: [PATCH 2/2] fix(docs): correct KeyAuthorization RLP encoding documentation - Fix SignedKeyAuthorization to show nested RLP structure - Add chain_id to KeyAuthorization examples - Correct signature type to include P256 and WebAuthn (not secp256k1 only) - Add missing fields (fee_payer_signature) to transaction examples - Remove redundant RLP field order note --- .../protocol/transactions/spec-tempo-transaction.mdx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/pages/protocol/transactions/spec-tempo-transaction.mdx b/docs/pages/protocol/transactions/spec-tempo-transaction.mdx index dcc6cbf909..29a4d3618c 100644 --- a/docs/pages/protocol/transactions/spec-tempo-transaction.mdx +++ b/docs/pages/protocol/transactions/spec-tempo-transaction.mdx @@ -436,7 +436,7 @@ rlp([ expiry?, // Optional trailing field (omitted or 0x80 if None) limits? // Optional trailing field (omitted or 0x80 if None) ]), - signature // PrimitiveSignature bytes (secp256k1 only for root key signatures) + signature // PrimitiveSignature bytes (secp256k1, P256, or WebAuthn) ]) ``` @@ -586,7 +586,7 @@ signed_key_authorization = rlp([ ]) ``` -The `signature` is a `PrimitiveSignature` (secp256k1 only) signed by the root key over the `key_authorization_digest`. +The `signature` is a `PrimitiveSignature` (secp256k1, P256, or WebAuthn) signed by the root key over the `key_authorization_digest`. Note: `expiry` and `limits` use RLP trailing field semantics - they can be omitted entirely when None. @@ -693,7 +693,7 @@ The protocol tracks and enforces spending limits for TIP20 token transfers: ```typescript // Define key parameters const keyAuth = { - chain_id: 42429, // Tempo chain ID (0 = valid on any chain) + chain_id: 42431, // Tempo chain ID (0 = valid on any chain) key_type: SignatureType.P256, // 1 key_id: keyId, // address derived from public key expiry: timestamp + 86400, // 24 hours from now (or null for never) @@ -716,7 +716,7 @@ The protocol tracks and enforces spending limits for TIP20 token transfers: 4. **Build TempoTransaction** ```typescript const tx = { - chain_id: 42429, // Tempo chain ID + chain_id: 42431, // Tempo chain ID nonce: await getNonce(account), nonce_key: 0, calls: [{ to: recipient, value: 0, input: "0x" }], @@ -731,7 +731,7 @@ The protocol tracks and enforces spending limits for TIP20 token transfers: key_authorization: { // SignedKeyAuthorization structure authorization: { - chain_id: 42429, + chain_id: 42431, key_type: keyAuth.key_type, key_id: keyAuth.key_id, expiry: keyAuth.expiry, @@ -742,8 +742,6 @@ The protocol tracks and enforces spending limits for TIP20 token transfers: }; ``` - > **Note:** When RLP encoding for `eth_sendRawTransaction`, the `key_authorization` field is encoded as a nested list: `rlp([rlp([chain_id, key_type, key_id, expiry?, limits?]), signature])`. The `key_authorization` comes **after** `aa_authorization_list` in the RLP field order. - 5. **Access Key Signs Transaction** ```typescript // Sign transaction with the NEW Access Key being authorized