Skip to content

Data Storage Guide

This guide explains how Autonomi handles data storage, including self-encryption and scratchpad features.

Self-Encryption

Self-encryption is a core feature that provides secure data storage by splitting and encrypting data into chunks.

How It Works

  1. Data is split into chunks
  2. Each chunk is encrypted
  3. A data map is created to track the chunks
  4. Additional encryption layers are added for larger files

Usage Examples

import { Client } from '@autonomi/client';

async function storeEncryptedData(data: Uint8Array) {
    const client = new Client();

    // Data is automatically self-encrypted when stored
    const address = await client.data_put_public(data);
    console.log(`Data stored at: ${address}`);

    // Retrieve and decrypt data
    const retrieved = await client.data_get_public(address);
    console.log('Data retrieved successfully');
}
from autonomi import Client

async def store_encrypted_data(data: bytes):
    client = Client()

    # Data is automatically self-encrypted when stored
    address = await client.data_put_public(data)
    print(f"Data stored at: {address}")

    # Retrieve and decrypt data
    retrieved = await client.data_get_public(address)
    print("Data retrieved successfully")
use autonomi::{Client, Bytes, Result};

async fn store_encrypted_data(data: Bytes) -> Result<()> {
    let client = Client::new()?;

    // Data is automatically self-encrypted when stored
    let address = client.data_put_public(data).await?;
    println!("Data stored at: {}", address);

    // Retrieve and decrypt data
    let retrieved = client.data_get_public(&address).await?;
    println!("Data retrieved successfully");

    Ok(())
}

Scratchpad

Scratchpad provides a mutable storage location for encrypted data with versioning support.

Features

  • Mutable data storage
  • Version tracking with monotonic counter
  • Owner-based access control
  • Data encryption
  • Signature verification

Usage Examples

import { Client, Scratchpad } from '@autonomi/client';

async function useScratchpad() {
    const client = new Client();
    const secretKey = await client.generate_secret_key();

    // Create or get existing scratchpad
    const [scratchpad, isNew] = await client.get_or_create_scratchpad(
        secretKey,
        42 // content type
    );

    // Update scratchpad data
    const data = new TextEncoder().encode('Hello World');
    await client.update_scratchpad(scratchpad, data, secretKey);

    // Read scratchpad data
    const retrieved = await client.get_scratchpad(scratchpad.address);
    const decrypted = await client.decrypt_scratchpad(retrieved, secretKey);
    console.log(new TextDecoder().decode(decrypted));
}
from autonomi import Client, Scratchpad

async def use_scratchpad():
    client = Client()
    secret_key = client.generate_secret_key()

    # Create or get existing scratchpad
    scratchpad, is_new = await client.get_or_create_scratchpad(
        secret_key,
        42  # content type
    )

    # Update scratchpad data
    data = b"Hello World"
    await client.update_scratchpad(scratchpad, data, secret_key)

    # Read scratchpad data
    retrieved = await client.get_scratchpad(scratchpad.address)
    decrypted = await client.decrypt_scratchpad(retrieved, secret_key)
    print(decrypted.decode())
use autonomi::{Client, Scratchpad, SecretKey, Bytes, Result};

async fn use_scratchpad() -> Result<()> {
    let client = Client::new()?;
    let secret_key = SecretKey::random();

    // Create or get existing scratchpad
    let (mut scratchpad, is_new) = client
        .get_or_create_scratchpad(&secret_key, 42)
        .await?;

    // Update scratchpad data
    let data = Bytes::from("Hello World");
    scratchpad.update_and_sign(data, &secret_key);

    // Store updated scratchpad
    client.put_scratchpad(&scratchpad).await?;

    // Read scratchpad data
    let retrieved = client.get_scratchpad(scratchpad.address()).await?;
    let decrypted = retrieved.decrypt_data(&secret_key)?;
    println!("Data: {}", String::from_utf8_lossy(&decrypted));

    Ok(())
}

Best Practices

  1. Version Management
  2. Always check the counter before updates
  3. Handle version conflicts appropriately
  4. Use monotonic counters for ordering

  5. Security

  6. Keep secret keys secure
  7. Verify signatures before trusting data
  8. Always encrypt sensitive data

  9. Error Handling

  10. Handle decryption failures gracefully
  11. Implement proper retry logic for network operations
  12. Validate data before storage

  13. Performance

  14. Cache frequently accessed data
  15. Batch updates when possible
  16. Monitor storage size

Implementation Details

Self-Encryption Process

  1. Data Splitting
// Internal process when storing data
let (data_map, chunks) = self_encryption::encrypt(data)?;
let (data_map_chunk, additional_chunks) = pack_data_map(data_map)?;
  1. Chunk Management
  2. Each chunk is stored separately
  3. Chunks are encrypted individually
  4. Data maps track chunk locations

Scratchpad Structure

pub struct Scratchpad {
    // Network address
    address: ScratchpadAddress,
    // Data type identifier
    data_encoding: u64,
    // Encrypted content
    encrypted_data: Bytes,
    // Version counter
    counter: u64,
    // Owner's signature
    signature: Option<Signature>,
}

Advanced Topics

Custom Data Types

You can use scratchpads to store any custom data type by implementing proper serialization:

#[derive(Serialize, Deserialize)]
struct CustomData {
    field1: String,
    field2: u64,
}

// Serialize before storing
let custom_data = CustomData {
    field1: "test".into(),
    field2: 42,
};
let bytes = serde_json::to_vec(&custom_data)?;
scratchpad.update_and_sign(Bytes::from(bytes), &secret_key);

Batch Operations

For better performance when dealing with multiple data items:

async fn batch_store(items: Vec<Bytes>) -> Result<Vec<ChunkAddress>> {
    let mut addresses = Vec::new();
    for item in items {
        let (data_map_chunk, chunks) = encrypt(item)?;
        // Store chunks in parallel
        futures::future::join_all(chunks.iter().map(|c| store_chunk(c))).await;
        addresses.push(data_map_chunk.address());
    }
    Ok(addresses)
}