Skip to content

feat(keychain): add access key signing support#11

Merged
grandizzy merged 9 commits intosdk-sync/add-devnet-testnet-cifrom
georgios/access-keys
Jan 29, 2026
Merged

feat(keychain): add access key signing support#11
grandizzy merged 9 commits intosdk-sync/add-devnet-testnet-cifrom
georgios/access-keys

Conversation

@gakonst
Copy link
Copy Markdown
Contributor

@gakonst gakonst commented Jan 29, 2026

Summary

Adds access key (Keychain) signing support to tempo-go, enabling transactions to be signed by delegated access keys on behalf of root accounts.

Motivation

Access keys allow a secondary signing key to sign transactions on behalf of a root account, with optional spending limits and expiry. This is essential for:

  • Delegated signing without passkey prompts on every transaction
  • Scoped permissions with per-token spending limits
  • Session keys for dApps

Changes

New pkg/keychain package

  • SignWithAccessKey(tx, accessKeySigner, rootAccount): Sign a transaction using an access key
  • BuildKeychainSignature(innerSig, rootAccount): Create 86-byte Keychain signature
  • ParseKeychainSignature(sig): Parse signature into components
  • VerifyAccessKeySignature(tx): Recover access key and root account addresses
  • Helper functions for AccountKeychain precompile interaction

Signature Format

Per Tempo spec, Keychain signatures are 86 bytes:

0x03 || root_account (20 bytes) || inner_signature (65 bytes)

Where:

  • 0x03 is the Keychain signature type identifier
  • root_account is the account the access key acts on behalf of
  • inner_signature is the secp256k1 signature from the access key (r || s || v)

Other Changes

  • pkg/signer/signer.go: Add Raw field to SignatureEnvelope for keychain signatures
  • pkg/transaction/serialize.go: Handle keychain signature serialization

Usage

import "github.com/tempoxyz/tempo-go/pkg/keychain"

// Create transaction
tx := transaction.New()
tx.ChainID = big.NewInt(42431)
tx.Calls = []transaction.Call{{To: &recipient, Value: big.NewInt(1000000)}}

// Sign with access key instead of root key
accessKeySigner, _ := signer.NewSigner(accessKeyPrivateKey)
err := keychain.SignWithAccessKey(tx, accessKeySigner, rootAccount)

Testing

All 10 unit tests pass:

go test ./pkg/keychain/... -v
=== RUN   TestBuildKeychainSignature
--- PASS: TestBuildKeychainSignature
=== RUN   TestParseKeychainSignature
--- PASS: TestParseKeychainSignature
=== RUN   TestSignWithAccessKey
--- PASS: TestSignWithAccessKey
=== RUN   TestVerifyAccessKeySignature
--- PASS: TestVerifyAccessKeySignature
...

References

Add pkg/keychain package for Tempo Access Key (Keychain) signatures:

- SignWithAccessKey: Sign transactions using an access key on behalf of a root account
- BuildKeychainSignature: Create 86-byte Keychain signature (0x03 || root || inner_sig)
- ParseKeychainSignature: Parse Keychain signature into components
- VerifyAccessKeySignature: Recover access key and root account from signature
- Helper functions for AccountKeychain precompile interaction

Changes:
- pkg/keychain/keychain.go: Core signing and verification functions
- pkg/keychain/helpers.go: Utility functions and constants
- pkg/keychain/doc.go: Package documentation
- pkg/keychain/keychain_test.go: Comprehensive unit tests
- pkg/signer/signer.go: Add Raw field to SignatureEnvelope for keychain sigs
- pkg/transaction/serialize.go: Handle keychain signature serialization

Keychain signature format per Tempo spec:
  0x03 || root_account (20 bytes) || inner_signature (65 bytes) = 86 bytes

Co-authored-by: George Matheos <george@tempo.xyz>
Amp-Thread-ID: https://ampcode.com/threads/T-019c0845-7af4-7389-ae71-f129787e7081
Co-authored-by: Amp <amp@ampcode.com>
@jenpaff jenpaff requested a review from brendanjryan January 29, 2026 11:37
@grandizzy grandizzy changed the base branch from main to sdk-sync/add-devnet-testnet-ci January 29, 2026 12:11
grandizzy and others added 8 commits January 29, 2026 16:38
Expand integration tests to match tempo-foundry's tempo-check.sh CI coverage,
similar to pytempo PR #7.

New integration tests:
- Node connectivity (client version check)
- Fee token liquidity (AlphaUSD, BetaUSD, ThetaUSD)
- Send with custom fee token
- 2D nonces (nonce_key)
- Expiring nonces (valid_before, valid_after)
- Sponsored transactions (fee payer)
- Batch transactions (multiple calls)
- setUserFeeToken

Skipped tests (require specific setup):
- Access keys (needs investigation on devnet)
- DEX operations (require liquidity setup)

CI changes:
- Integration tests (Devnet/Testnet) now run on all PRs, not just main/schedule

Test results: 12 passed, 3 skipped

Amp-Thread-ID: https://ampcode.com/threads/T-019c0a32-581b-717c-b814-0d5614d0ff97
Co-authored-by: Amp <amp@ampcode.com>
…ress)

Localnet only runs TestNodeConnection, BuilderValidation, and RoundTrip
since dev node lacks tempo_fundAddress RPC for funding accounts.
Devnet/testnet run full integration tests with tempo_fundAddress.

Amp-Thread-ID: https://ampcode.com/threads/T-019c0a32-581b-717c-b814-0d5614d0ff97
Co-authored-by: Amp <amp@ampcode.com>
Use verbose output for better debugging in CI logs.

Amp-Thread-ID: https://ampcode.com/threads/T-019c0a32-581b-717c-b814-0d5614d0ff97
Co-authored-by: Amp <amp@ampcode.com>
… gas

The access key authorization requires:
1. Standard EIP-1559 transaction (not Tempo tx) for authorizeKey call
2. Higher gas limit (~600k) for the authorization tx
3. Dynamic gas price from network

Test results: 13 passed, 2 skipped

Amp-Thread-ID: https://ampcode.com/threads/T-019c0a32-581b-717c-b814-0d5614d0ff97
Co-authored-by: Amp <amp@ampcode.com>
Replace time.Sleep(2s) with waitForReceipt that polls up to 30 seconds.
All tests now explicitly assert receipt status is 0x1 (success).
Refactored formatReceipt to accept receipt map instead of fetching.

Amp-Thread-ID: https://ampcode.com/threads/T-019c0a6d-ce6b-74cd-83ac-70d77d77ea1e
Co-authored-by: Amp <amp@ampcode.com>
- Replace hardcoded 10 gwei gas price with dynamic getGasPrice() + 50% buffer
- Fix mint selector: 0x0b39c529 -> 0xf1aa8cb8
- Fix setUserToken selector: 0x8a7fcf3f -> 0xe7897444
- Increase gas limit to 500k-600k for FeeController precompile calls
  (cold storage access on first call requires ~520k gas)

Amp-Thread-ID: https://ampcode.com/threads/T-019c0a6d-ce6b-74cd-83ac-70d77d77ea1e
Co-authored-by: Amp <amp@ampcode.com>
@grandizzy grandizzy marked this pull request as ready for review January 29, 2026 16:32
@grandizzy grandizzy merged commit 5567dc8 into sdk-sync/add-devnet-testnet-ci Jan 29, 2026
13 of 16 checks passed
@grandizzy grandizzy deleted the georgios/access-keys branch January 29, 2026 16:33
Copy link
Copy Markdown
Collaborator

@brendanjryan brendanjryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

on:
push:
branches: [ main, master ]
branches: [ main ]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants