Keystore
The Keystore interface and MemKeystore implementation provide flexible key management for Starknet accounts.
Keystore Interface
The Keystore interface defines the contract for key storage and signing implementations.
Interface Definition
type Keystore interface {
Sign(ctx context.Context, id string, msgHash *big.Int) (x, y *big.Int, err error)
}Method
Sign
Signs a message hash using a stored private key.
Signature:Sign(ctx context.Context, id string, msgHash *big.Int) (x, y *big.Int, err error)ctx- Context for cancellation and timeoutid- Identifier for the key (typically the public key)msgHash- Message hash to sign
x- X coordinate (r) of the signaturey- Y coordinate (s) of the signatureerr- Error if signing fails
Implementing Custom Keystores
You can implement the Keystore interface to create custom key storage solutions:
package main
import (
"context"
"math/big"
"github.com/NethermindEth/starknet.go/curve"
)
// SecureKeystore implements the Keystore interface with encryption
type SecureKeystore struct {
encryptedKeys map[string][]byte
// Add your encryption/decryption logic
}
func (sk *SecureKeystore) Sign(
ctx context.Context,
id string,
msgHash *big.Int,
) (x, y *big.Int, err error) {
// 1. Decrypt the private key
encryptedKey, exists := sk.encryptedKeys[id]
if !exists {
return nil, nil, fmt.Errorf("key not found: %s", id)
}
privateKey := sk.decrypt(encryptedKey) // Your decryption logic
// 2. Check context cancellation
select {
case <-ctx.Done():
return nil, nil, ctx.Err()
default:
}
// 3. Sign the message
x, y, err = curve.Sign(msgHash, privateKey)
return x, y, err
}Use Cases
Custom Keystore implementations can:
- Encrypt keys at rest
- Store keys in hardware security modules (HSM)
- Use cloud key management services (AWS KMS, Google Cloud KMS)
- Integrate with existing key management infrastructure
- Implement multi-signature schemes
MemKeystore
In-memory keystore implementation for development and testing.
Type Definition
type MemKeystore struct {
mu sync.Mutex
keys map[string]*big.Int
}Description
MemKeystore is a thread-safe, in-memory implementation of the Keystore interface. It stores private keys in a map without encryption or persistence.
Features:- Thread-safe with mutex protection
- Simple key-value storage
- No persistence (keys lost on exit)
- No encryption
- Testing and development
- Examples and tutorials
- Temporary accounts
- Production use
- Long-lived accounts
- Accounts with real value
Methods
Put
Stores a private key in the keystore.
Signature:func (ks *MemKeystore) Put(senderAddress string, k *big.Int)senderAddress- Identifier for the key (typically public key)k- Private key to store
package main
import (
"fmt"
"math/big"
"github.com/NethermindEth/starknet.go/account"
)
func main() {
ks := account.NewMemKeystore()
// Store a key
publicKey := "0x1234..."
privateKey := new(big.Int).SetUint64(12345)
ks.Put(publicKey, privateKey)
fmt.Println("Key stored successfully")
}Get
Retrieves a private key from the keystore.
Signature:func (ks *MemKeystore) Get(senderAddress string) (*big.Int, error)senderAddress- Identifier for the key
*big.Int- Private keyerror- Error if key doesn't exist
package main
import (
"fmt"
"log"
"math/big"
"github.com/NethermindEth/starknet.go/account"
)
func main() {
ks := account.NewMemKeystore()
// Store a key
publicKey := "0x1234..."
privateKey := new(big.Int).SetUint64(12345)
ks.Put(publicKey, privateKey)
// Retrieve the key
retrievedKey, err := ks.Get(publicKey)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Retrieved key: %s\n", retrievedKey.String())
}key, err := ks.Get(publicKey)
if err != nil {
if errors.Is(err, account.ErrSenderNoExist) {
// Handle missing key
fmt.Println("Key not found")
}
return err
}Sign
Signs a message hash using a stored private key.
Signature:func (ks *MemKeystore) Sign(
ctx context.Context,
id string,
msgHash *big.Int,
) (r, s *big.Int, err error)ctx- Context for cancellation and timeoutid- Identifier for the key (typically public key)msgHash- Message hash to sign
r- R component of the signatures- S component of the signatureerr- Error if signing fails
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/NethermindEth/starknet.go/account"
)
func main() {
// Create keystore with keys
ks, pubKey, _ := account.GetRandomKeys()
// Message to sign
msgHash := new(big.Int).SetUint64(42)
// Sign the message
r, s, err := ks.Sign(context.Background(), pubKey.String(), msgHash)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Signature r: %s\n", r.String())
fmt.Printf("Signature s: %s\n", s.String())
}// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Sign with timeout
r, s, err := ks.Sign(ctx, publicKey, msgHash)
if err != nil {
if err == context.DeadlineExceeded {
fmt.Println("Signing timed out")
}
return err
}Get
Retrieves a private key from the keystore by sender address.
Method Signature:func (ks *MemKeystore) Get(senderAddress string) (*big.Int, error)senderAddress- Address of the sender as string
*big.Int- Private key associated with the addresserror- ErrSenderNoExist if address not found
package main
import (
"fmt"
"log"
"math/big"
"github.com/NethermindEth/starknet.go/account"
)
func main() {
// Create keystore and add a key
ks := account.NewMemKeystore()
address := "0x1234567890abcdef"
privateKey := new(big.Int).SetUint64(42)
// Store the key
ks.Put(address, privateKey)
// Retrieve the key
retrievedKey, err := ks.Get(address)
if err != nil {
log.Fatal("Key not found:", err)
}
fmt.Printf("Retrieved private key: %s\n", retrievedKey.String())
// Try to get non-existent key
_, err = ks.Get("0xnonexistent")
if err != nil {
fmt.Println("Expected error:", err) // Will print: sender does not exist
}
}Put
Stores a private key in the keystore for a given sender address.
Method Signature:func (ks *MemKeystore) Put(senderAddress string, k *big.Int)senderAddress- Address of the sender as stringk- Private key to store as *big.Int
package main
import (
"fmt"
"math/big"
"github.com/NethermindEth/starknet.go/account"
)
func main() {
// Create a new keystore
ks := account.NewMemKeystore()
// Multiple addresses and keys
addresses := []string{
"0x1234567890abcdef",
"0xfedcba0987654321",
"0xaabbccddeeff0011",
}
// Store multiple keys
for i, addr := range addresses {
privateKey := new(big.Int).SetUint64(uint64(i + 1) * 100)
ks.Put(addr, privateKey)
fmt.Printf("Stored key for %s\n", addr)
}
// Verify all keys are stored
for _, addr := range addresses {
key, err := ks.Get(addr)
if err != nil {
fmt.Printf("Error getting key for %s: %v\n", addr, err)
} else {
fmt.Printf("Key for %s: %s\n", addr, key.String())
}
}
}Complete Usage Example
This example demonstrates the full lifecycle of using MemKeystore with an Account.
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/starknet.go/account"
"github.com/NethermindEth/starknet.go/rpc"
"github.com/NethermindEth/starknet.go/utils"
)
func main() {
// 1. Create keystore and generate keys
ks, pubKey, privKey := account.GetRandomKeys()
fmt.Printf("Generated keys:\n")
fmt.Printf(" Public: %s\n", pubKey.String())
fmt.Printf(" Private: %s\n", privKey.String())
// 2. Or use existing keys
existingPubKey := "0x1234..."
existingPrivKey, _ := new(big.Int).SetString("0xabcd...", 16)
ks2 := account.SetNewMemKeystore(existingPubKey, existingPrivKey)
// 3. Create RPC provider
provider, err := rpc.NewProvider("https://starknet-sepolia.public.blastapi.io/rpc/v0_8")
if err != nil {
log.Fatal(err)
}
// 4. Create account with keystore
accountAddress, _ := utils.HexToFelt("0x1234...")
acc, err := account.NewAccount(
provider,
accountAddress,
pubKey.String(),
ks,
account.CairoV2,
)
if err != nil {
log.Fatal(err)
}
// 5. Use account to sign messages
message := new(felt.Felt).SetUint64(42)
signature, err := acc.Sign(context.Background(), message)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Signature: [%s, %s]\n", signature[0].String(), signature[1].String())
// 6. Verify signature
valid, err := acc.Verify(message, signature)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Signature valid: %t\n", valid)
// 7. Sign and send transactions
functionCalls := []rpc.InvokeFunctionCall{
{
ContractAddress: contractAddress,
EntryPointSelector: entryPointSelector,
Calldata: calldata,
},
}
// The keystore is automatically used by BuildAndSendInvokeTxn
response, err := acc.BuildAndSendInvokeTxn(
context.Background(),
functionCalls,
nil, // Use default options
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Transaction hash: %s\n", response.TransactionHash)
}Security Considerations
MemKeystore Security
MemKeystore has several limitations:
- No Encryption: Keys stored in plain text in memory
- No Persistence: Keys lost when program exits
- Memory Exposure: Keys may be visible in memory dumps
- No Access Control: Any code with keystore reference can access keys
Production Recommendations
For production use, implement a secure Keystore:
// Example: Encrypted file-based keystore
type FileKeystore struct {
filepath string
passphrase string
}
func (fk *FileKeystore) Sign(
ctx context.Context,
id string,
msgHash *big.Int,
) (x, y *big.Int, err error) {
// 1. Load encrypted key file
// 2. Decrypt using passphrase
// 3. Sign message
// 4. Clear decrypted key from memory
// 5. Return signature
}Best Practices
- Never hardcode private keys in source code
- Use environment variables or secure configuration for keys
- Implement key rotation for long-lived accounts
- Use hardware security modules for high-value accounts
- Encrypt keys at rest in any persistent storage
- Limit key access to minimum necessary code
- Log security events (key access, failed signatures)
- Implement rate limiting to prevent brute force attacks
Related Resources
- Account Functions - Functions for creating keystores
- Account Methods - Methods that use keystores
- Curve Package - Cryptographic operations

