Skip to main content

Overview

This document describes the complete data flow for all major operations in Salud Health, from user interaction to blockchain storage.

System Data Flow Diagram

1. Wallet Connection Flow

Patient Connects Wallet

Data Transformation:
// Input
privateKey: "APrivateKey1zkp..."

// Backend Processing
const account = Account.fromPrivateKey(privateKey);
const sessionId = generateUUID(); // "550e8400-e29b-41d4-a716-446655440000"
const address = account.address().toString(); // "aleo1..."
const viewKey = account.viewKey().toString(); // "AViewKey1..."

// Output
{
  sessionId: "550e8400-e29b-41d4-a716-446655440000",
  address: "aleo1gl4a57rcxyjvmzcgjscjqe466ecdr7uk4gdp7sf5pctu6tjvv5qs60lw8y",
  viewKey: "AViewKey1..."
}

2. Create Medical Record Flow

Complete Data Pipeline

Step-by-Step Data Transformation

Step 1: User Input → JSON

// Frontend: src/lib/aleo-utils.ts:124
function createRecordData(title: string, description: string): string {
  return JSON.stringify({
    t: title,
    d: description
  });
}

// Example
Input:
  title: "Annual Checkup 2024"
  description: "Blood pressure: 120/80, Heart rate: 72 bpm, No issues detected"

Output:
  '{"t":"Annual Checkup 2024","d":"Blood pressure: 120/80, Heart rate: 72 bpm, No issues detected"}'

Step 2: String → Field Elements

// Frontend: src/lib/aleo-utils.ts:16
function stringToFieldElements(data: string): string[] {
  const encoder = new TextEncoder();
  const bytes = encoder.encode(data); // UTF-8 bytes
  
  const fields: string[] = [];
  const BYTES_PER_FIELD = 30; // ~253 bits per field
  const NUM_FIELD_PARTS = 12; // Total fields (v6 contract)
  
  for (let i = 0; i < NUM_FIELD_PARTS; i++) {
    const start = i * BYTES_PER_FIELD;
    const end = Math.min(start + BYTES_PER_FIELD, bytes.length);
    const part = bytes.slice(start, end);
    fields.push(bytesToField(part)); // Convert to "123456field"
  }
  
  while (fields.length < NUM_FIELD_PARTS) {
    fields.push('0field'); // Pad remaining
  }
  
  return fields;
}

// Example Output
[
  "123456789012345678901234567890field",  // data_part1
  "987654321098765432109876543210field",  // data_part2
  "456789012345678901234567890123field",  // data_part3
  // ... 9 more parts
  "0field",  // data_part12 (padding if data < 360 bytes)
]

Step 3: Generate Hash and Nonce

// Hash data for integrity verification
// Frontend: src/lib/aleo-utils.ts:92
function hashData(data: string): string {
  const encoder = new TextEncoder();
  const bytes = encoder.encode(data);
  
  let hash = BigInt(0);
  for (let i = 0; i < bytes.length; i++) {
    hash = hash + BigInt(bytes[i]) * BigInt(i + 1);
  }
  hash = hash * BigInt(31) + BigInt(17);
  
  return `${hash.toString()}field`;
}

// Generate cryptographically random nonce
// Frontend: src/lib/aleo-utils.ts:108
function generateNonce(): string {
  const randomBytes = new Uint8Array(16);
  crypto.getRandomValues(randomBytes); // Browser crypto API
  
  let nonce = BigInt(0);
  for (let i = 0; i < randomBytes.length; i++) {
    nonce = (nonce << BigInt(8)) | BigInt(randomBytes[i]);
  }
  
  return `${nonce.toString()}field`;
}

// Example
data_hash: "1234567890123456789field"
nonce: "98765432109876543210987654321field"

Step 4: Backend Transaction Execution

// Backend: Prepare transaction
const inputs = [
  dataPart1, dataPart2, dataPart3, dataPart4,
  dataPart5, dataPart6, dataPart7, dataPart8,
  dataPart9, dataPart10, dataPart11, dataPart12,
  `${recordType}u8`,        // e.g., "1u8" for General Health
  dataHash,                 // "1234567890123456789field"
  nonce,                    // Random field element
  makeDiscoverable ? 'true' : 'false'
];

const transaction = await aleoSDK.execute(
  'salud_health_records_v6.aleo',
  'create_record',
  inputs,
  account.privateKey()
);

Step 5: Smart Contract Processing

// Contract: src/main.leo:163
async transition create_record(
    data_part1: field,
    // ... data_part2-12
    record_type: u8,
    data_hash: field,
    nonce: field,
    make_discoverable: bool
) -> (MedicalRecord, Future) {
    // 1. Validate record type
    assert(record_type >= 1u8 && record_type <= 10u8);
    
    // 2. Generate unique record ID
    let record_id: field = BHP256::hash_to_field(RecordIdInput {
        patient: self.caller,
        data_hash: data_hash,
        nonce: nonce,
    });
    
    // 3. Create private record (owned by patient)
    let medical_record: MedicalRecord = MedicalRecord {
        owner: self.caller,
        record_id: record_id,
        data_hash: data_hash,
        data_part1: data_part1,
        // ... data_part2-12
        record_type: record_type,
        created_at: 0u32,
        version: 1u8,
    };
    
    // 4. Return record + async finalize
    return (medical_record, finalize_create_record(...));
}

// 5. Update public mappings
async function finalize_create_record(
    patient: address,
    record_id: field,
    record_type: u8,
    make_discoverable: bool
) {
    // Increment patient's record count
    let count = patient_record_count.get_or_use(patient as field, 0u64);
    patient_record_count.set(patient as field, count + 1u64);
    
    // Optionally add to public index
    if make_discoverable {
        record_metadata.set(record_id, RecordMetadata {
            patient: patient,
            record_id: record_id,
            record_type: record_type,
            created_at: block.height,
            is_active: true,
        });
    }
}

Step 6: Response & Caching

// Backend response
{
  success: true,
  data: {
    transactionId: "at1...",
    recordId: "1234567890123456789field"
  }
}

// Frontend caches in IndexedDB
const cachedRecord = {
  id: generateLocalId(),
  recordId: "1234567890123456789field",
  title: "Annual Checkup 2024",
  description: "Blood pressure: 120/80...",
  recordType: 1,
  createdAt: new Date().toISOString(),
  ownerAddress: userAddress,
  isEncrypted: false,
};

await db.records.add(cachedRecord);

3. Grant Access Flow

Access Token Generation Pipeline

Access Token Data Structure

// QR Code Payload
interface QRCodeData {
  recordId: string;      // "1234567890field"
  accessToken: string;   // "9876543210field"
  expiresAt: number;     // Block height: 12345
  patientAddress: string; // "aleo1..."
}

// On-Chain AccessGrant (stored in public mapping)
struct AccessGrant {
  patient: address;      // aleo1abc...
  doctor: address;       // aleo1xyz...
  record_id: field;      // 1234567890field
  access_token: field;   // 9876543210field (hash of above + nonce)
  granted_at: u32;       // Block height when granted: 10000
  expires_at: u32;       // Block height when expires: 15760 (24h later)
  is_revoked: bool;      // false
}

4. Doctor Access Verification Flow

Verification Logic (Smart Contract)

// Contract: src/main.leo:342
async transition verify_access(
    access_token: field,
    doctor: address,
    record_id: field
) -> Future {
    return finalize_verify_access(access_token, doctor, record_id);
}

async function finalize_verify_access(
    access_token: field,
    doctor: address,
    record_id: field
) {
    // Step 1: Check token exists
    let is_valid: bool = access_token_valid.get_or_use(access_token, false);
    assert(is_valid); // ❌ Fails if token not found
    
    // Step 2: Get full grant details
    let grant: AccessGrant = access_grants.get(access_token);
    
    // Step 3: Verify doctor address
    assert_eq(grant.doctor, doctor); // ❌ Fails if wrong doctor
    
    // Step 4: Verify record ID
    assert_eq(grant.record_id, record_id); // ❌ Fails if wrong record
    
    // Step 5: Check not revoked
    assert(!grant.is_revoked); // ❌ Fails if revoked
    
    // Step 6: Check not expired
    assert(block.height <= grant.expires_at); // ❌ Fails if expired
    
    // ✅ All checks pass - transaction succeeds
}

5. Record Fetching Flow

Blockchain → Frontend Pipeline

Field Element Decoding

// Backend: Convert 12 field elements → string
function fieldElementsToString(fields: string[]): string {
  const allBytes: number[] = [];
  
  for (const fieldStr of fields) {
    // "123456field" → 123456n (BigInt)
    const valueStr = fieldStr.replace('field', '');
    if (valueStr === '0') continue; // Skip padding
    
    let value = BigInt(valueStr);
    const bytes: number[] = [];
    
    // Convert BigInt → bytes
    while (value > 0n) {
      bytes.unshift(Number(value & 0xFFn));
      value = value >> 8n;
    }
    
    allBytes.push(...bytes);
  }
  
  // bytes → UTF-8 string
  const decoder = new TextDecoder();
  return decoder.decode(new Uint8Array(allBytes));
}

// Example
Input: [
  "123456789012345678901234567890field",
  "987654321098765432109876543210field",
  "0field", // padding
  // ... more fields
]

Output: '{"t":"Annual Checkup 2024","d":"Blood pressure: 120/80..."}'

// Then parse JSON
const parsed = JSON.parse(output);
// { t: "Annual Checkup 2024", d: "Blood pressure: 120/80..." }

Data Flow Summary

Operation Comparison

OperationFrontend → BackendBackend → BlockchainBlockchain StorageResponse Time
Connect WalletPrivate keyDerive accountNoneLess than 1s
Create Record12 fields + metadataExecute create_recordPrivate record + mappings~15s (1 block)
Grant AccessrecordId + doctorExecute grant_accessAccessGrant mapping~15s
Verify Accesstoken + doctorExecute verify_accessRead-only~15s
Fetch RecordssessionIdQuery with view keyRead private records5-10s
Revoke AccessaccessTokenExecute revoke_accessUpdate mapping~15s

Data Size Limits

┌─────────────────────────────────────────┐
│ Medical Record Storage Capacity         │
├─────────────────────────────────────────┤
│ Field Elements: 12                      │
│ Bytes per Field: ~30                    │
│ Total Capacity: ~360 bytes              │
│                                         │
│ Suitable for:                           │
│ ✅ Prescriptions (~200 bytes)           │
│ ✅ Lab results (~300 bytes)             │
│ ✅ Vaccination records (~150 bytes)     │
│ ✅ Basic medical notes (~350 bytes)     │
│                                         │
│ Not suitable for:                       │
│ ❌ X-ray images (MBs)                   │
│ ❌ Full medical history (KBs)           │
│ ❌ Lengthy diagnoses (>360 bytes)       │
│                                         │
│ Solution: Use multiple records or       │
│ off-chain storage (IPFS) with on-chain │
│ hash for large data                     │
└─────────────────────────────────────────┘

Performance Optimization

Frontend Caching Strategy

// On initial load
1. Check IndexedDB for cached records
2. Display cached records immediately (fast UX)
3. Fetch from blockchain in background
4. Update cache with latest data
5. Refresh UI if changes detected

// On record creation
1. Submit transaction
2. Immediately add to cache (optimistic UI)
3. Wait for blockchain confirmation
4. Update cache with final recordId
5. Show success notification

Backend Session Management

// In-memory session store
const sessions = new Map<string, {
  account: Account,
  address: string,
  viewKey: string,
  createdAt: number,
}>;

// Auto-cleanup after 24 hours
setInterval(() => {
  const now = Date.now();
  for (const [id, session] of sessions) {
    if (now - session.createdAt > 24 * 60 * 60 * 1000) {
      sessions.delete(id);
    }
  }
}, 60 * 60 * 1000); // Check every hour

Next Steps

Encryption Details

Deep dive into encryption implementation

Smart Contract

Leo smart contract documentation

Build docs developers (and LLMs) love