Key Management
Proper key management is crucial for Tongo applications. This page covers best practices for generating, storing, and deriving Tongo private keys.
Key Types
Tongo Private Key
- Purpose: Decrypt balances and authorize Tongo operations
- Format:
bigintorUint8Array - Range: Must be within the Stark curve scalar field
- Example:
82130983n
Tongo Public Key
- Purpose: Account identifier for receiving transfers
- Format:
{ x: bigint, y: bigint }(elliptic curve point) - Derivation: Computed as
g^privateKeywheregis the curve generator
Tongo Address
- Purpose: Human-readable account identifier
- Format: Base58-encoded public key
- Example:
"Um6QEVHZaXkii8hWzayJf6PBWrJCTuJomAst75Zmy12"
Key Generation Strategies
Strategy 1: Random Generation
Generate a completely random private key:
import { getRandomValues } from "crypto";
function generateRandomTongoKey(): bigint {
// Generate random bytes
const bytes = new Uint8Array(32);
getRandomValues(bytes);
// Convert to bigint
const privateKey = BigInt('0x' + Buffer.from(bytes).toString('hex'));
// Ensure it's within the curve order
const CURVE_ORDER = BigInt('0x800000000000010ffffffffffffffffb781126dcae7b2321e66a241adc64d2f');
return privateKey % CURVE_ORDER;
}
const randomKey = generateRandomTongoKey();
Pros:
- Maximum entropy
- No dependencies
Cons:
- Must be securely stored and backed up
- No deterministic recovery
Strategy 2: Deterministic Derivation from Starknet Wallet
Derive your Tongo key deterministically from your Starknet wallet signature:
import { AccountInterface, TypedData, hash } from "starknet";
const CURVE_ORDER = BigInt('0x800000000000010ffffffffffffffffb781126dcae7b2321e66a241adc64d2f');
async function deriveTongoPrivateKey(account: AccountInterface): Promise<bigint> {
const accountAddress = account.address;
const chainId = (account as any).chainId || 'SN_SEPOLIA';
// Create typed data for signing
const typedData: TypedData = {
domain: {
name: 'Tongo Key Derivation',
version: '1',
chainId: chainId,
},
types: {
StarkNetDomain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'felt' },
],
Message: [
{ name: 'action', type: 'felt' },
{ name: 'wallet', type: 'felt' },
],
},
primaryType: 'Message',
message: {
action: 'tongo-keygen-v1',
wallet: accountAddress,
},
};
// Sign the typed data
const signature = await account.signMessage(typedData);
// Extract r and s from signature
const { r, s } = extractSignatureComponents(signature);
// Hash using Poseidon
const privateKeyHex = hash.computePoseidonHashOnElements([r, s]);
let privateKey = BigInt(privateKeyHex);
// Reduce modulo curve order
privateKey = privateKey % CURVE_ORDER;
// Ensure non-zero
if (privateKey === BigInt(0)) {
throw new Error('Derived private key is zero');
}
return privateKey;
}
function extractSignatureComponents(signature: any): { r: bigint; s: bigint } {
if (Array.isArray(signature)) {
// Argent X format: [1, 0, r, s, ...]
if (signature.length >= 4 && BigInt(signature[0]) === BigInt(1)) {
return {
r: BigInt(signature[2]),
s: BigInt(signature[3]),
};
}
// Standard format: [r, s]
if (signature.length >= 2) {
return {
r: BigInt(signature[0]),
s: BigInt(signature[1]),
};
}
}
if (signature && typeof signature === 'object' && 'r' in signature && 's' in signature) {
return {
r: BigInt(signature.r),
s: BigInt(signature.s),
};
}
throw new Error('Invalid signature format');
}
Pros:
- Deterministic (same wallet → same Tongo key)
- No need to store Tongo private key separately
- Can be recovered from wallet signature
Cons:
- Requires user to sign a message
- Depends on wallet connection
Usage in React
import { useAccount } from '@starknet-react/core';
import { useState } from 'react';
function useTongoKey() {
const { account } = useAccount();
const [tongoKey, setTongoKey] = useState<bigint | null>(null);
const [loading, setLoading] = useState(false);
async function deriveKey() {
if (!account) {
throw new Error('No wallet connected');
}
setLoading(true);
try {
const key = await deriveTongoPrivateKey(account);
setTongoKey(key);
return key;
} finally {
setLoading(false);
}
}
return { tongoKey, deriveKey, loading };
}
Storage Best Practices
Never Do This
// DON'T: Store in localStorage unencrypted
localStorage.setItem('tongoKey', privateKey.toString());
// DON'T: Hard-code in source
const PRIVATE_KEY = 82130983n;
// DON'T: Send over network
fetch('/api/save-key', { body: privateKey });
Recommended Approaches
For Web Applications
// Derive from wallet each time
async function getTongoAccount(walletAccount, provider) {
const privateKey = await deriveTongoPrivateKey(walletAccount);
return new TongoAccount(privateKey, tongoAddress, provider);
}
// Or encrypt before storing
import { encrypt, decrypt } from 'your-encryption-library';
function saveEncryptedKey(privateKey: bigint, password: string) {
const encrypted = encrypt(privateKey.toString(), password);
localStorage.setItem('tongoKey_encrypted', encrypted);
}
function loadEncryptedKey(password: string): bigint | null {
const encrypted = localStorage.getItem('tongoKey_encrypted');
if (!encrypted) return null;
const decrypted = decrypt(encrypted, password);
return BigInt(decrypted);
}
For Backend Applications
// Use environment variables
const TONGO_PRIVATE_KEY = BigInt(process.env.TONGO_PRIVATE_KEY!);
// Or use a secret management service
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
async function getTongoKey() {
const client = new SecretManagerServiceClient();
const [version] = await client.accessSecretVersion({
name: 'projects/my-project/secrets/tongo-key/versions/latest',
});
const payload = version.payload?.data?.toString();
return BigInt(payload!);
}
Key Backup and Recovery
Backup Strategies
-
Hardware Wallet Integration
- Store derivation parameters in hardware wallet
- Sign derivation message when needed
-
Encrypted Backup
- Encrypt private key with strong password
- Store encrypted backup securely
- Test recovery before relying on it
-
Multi-Signature Schemes
- Split key using Shamir's Secret Sharing
- Require M-of-N shares to recover
- Distribute shares to trusted parties
Recovery Checklist
If using deterministic derivation:
- Can access Starknet wallet
- Know which wallet was used
- Can sign messages with wallet
If using random generation:
- Have encrypted backup
- Remember encryption password
- Can access backup location
Security Considerations
Key Lifecycle
-
Generation
- Use cryptographically secure randomness
- Or derive deterministically from wallet
-
Usage
- Never log or print the key
- Keep in memory only when needed
- Clear from memory after use
-
Storage
- Encrypt at rest
- Use secure key management systems
- Regular security audits
-
Disposal
- Securely wipe from memory
- Delete encrypted backups if desired
- Withdraw all funds first
Common Pitfalls
Weak entropy: Using predictable values like timestamps or counters
Key reuse: Using same key across different chains or applications
Insecure storage: Storing keys in plain text or weakly encrypted
No backup: Losing access to funds if key is lost
Best practice: Use wallet-derived keys with secure backup
Testing Keys
For development and testing only:
// Test keys (NEVER use in production!)
const TEST_KEYS = {
alice: 82130983n,
bob: 12930923n,
charlie: 55555555n,
};
// Use only on testnets
const testAccount = new TongoAccount(
TEST_KEYS.alice,
SEPOLIA_TONGO_ADDRESS,
sepoliaProvider
);
Multi-Account Management
Managing multiple Tongo accounts:
interface TongoAccountInfo {
name: string;
privateKey: bigint;
address: string;
}
class TongoWallet {
private accounts: Map<string, TongoAccountInfo> = new Map();
async addAccount(name: string, privateKey: bigint) {
const account = new TongoAccount(privateKey, tongoAddress, provider);
const address = account.tongoAddress();
this.accounts.set(name, {
name,
privateKey,
address
});
}
getAccount(name: string): TongoAccount | null {
const info = this.accounts.get(name);
if (!info) return null;
return new TongoAccount(info.privateKey, tongoAddress, provider);
}
listAccounts(): string[] {
return Array.from(this.accounts.keys());
}
}