Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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: bigint or Uint8Array
  • 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^privateKey where g is 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 });

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

  1. Hardware Wallet Integration

    • Store derivation parameters in hardware wallet
    • Sign derivation message when needed
  2. Encrypted Backup

    • Encrypt private key with strong password
    • Store encrypted backup securely
    • Test recovery before relying on it
  3. 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

  1. Generation

    • Use cryptographically secure randomness
    • Or derive deterministically from wallet
  2. Usage

    • Never log or print the key
    • Keep in memory only when needed
    • Clear from memory after use
  3. Storage

    • Encrypt at rest
    • Use secure key management systems
    • Regular security audits
  4. 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());
    }
}

Next Steps