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
- Data is split into chunks
- Each chunk is encrypted
- A data map is created to track the chunks
- 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
- Version Management
- Always check the counter before updates
- Handle version conflicts appropriately
-
Use monotonic counters for ordering
-
Security
- Keep secret keys secure
- Verify signatures before trusting data
-
Always encrypt sensitive data
-
Error Handling
- Handle decryption failures gracefully
- Implement proper retry logic for network operations
-
Validate data before storage
-
Performance
- Cache frequently accessed data
- Batch updates when possible
- Monitor storage size
Implementation Details
Self-Encryption Process
- 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)?;
- Chunk Management
- Each chunk is stored separately
- Chunks are encrypted individually
- 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)
}