Skip to main content

Overview

The verify_access transition allows healthcare providers (or the system) to verify that they have valid access to a specific medical record. This is a verification operation - the transaction succeeds only if all access conditions are met. If any validation fails, the transaction is rejected.

Function Signature

async transition verify_access(
    access_token: field,
    doctor: address,
    record_id: field
) -> Future
Source: main.leo:342-349

Parameters

access_token
field
required
The cryptographic access token received from the patient’s QR code. This token was generated by the grant_access function and serves as proof of permission.
doctor
address
required
The healthcare provider’s Aleo address. Must match the address specified when the access was granted. This prevents token sharing between different doctors.
record_id
field
required
The unique identifier of the medical record being accessed. Must match the record ID specified when the access was granted. This prevents using a token for a different record.

Returns

Future
Future
Async finalization that performs all verification checks. The transaction:
  • Succeeds if all validations pass (access is valid)
  • Fails if any validation fails (access is denied)
There is no explicit return value - success/failure of the transaction indicates access validity.

Validation Checks

The finalize function performs the following checks in order:

1. Token Exists and Is Valid

let is_valid: bool = access_token_valid.get_or_use(access_token, false);
assert(is_valid);
Fails if the token doesn’t exist in the mapping or has been marked invalid.

2. Get Access Grant

let grant: AccessGrant = access_grants.get(access_token);
Fails if the token is not found in the access_grants mapping.

3. Doctor Address Match

assert_eq(grant.doctor, doctor);
Fails if the doctor address doesn’t match the one specified in the grant. Prevents token sharing.

4. Record ID Match

assert_eq(grant.record_id, record_id);
Fails if the record ID doesn’t match the one specified in the grant. Prevents using a token for different records.

5. Not Revoked

assert(!grant.is_revoked);
Fails if the patient has manually revoked the access.

6. Not Expired

assert(block.height <= grant.expires_at);
Fails if the current block height exceeds the expiration block height.

Code Example

import { AleoClient } from '@aleo/sdk';

// Initialize Aleo client
const client = new AleoClient();

// Doctor scans patient's QR code
const qrData = await scanQRCode();
const { token, recordId } = parseQRData(qrData);

// Doctor's own address
const doctorAddress = client.address;

try {
  // Attempt to verify access
  await client.execute(
    'salud_health_records_v6.aleo',
    'verify_access',
    [
      token,          // Access token from QR code
      doctorAddress,  // Doctor's address
      recordId        // Record ID from QR code
    ]
  );

  // Transaction succeeded - access is valid!
  console.log('Access verified successfully');
  
  // Now the doctor can fetch and decrypt the medical record
  const encryptedRecord = await fetchMedicalRecord(recordId);
  const decryptedData = await decryptRecord(encryptedRecord, doctorPrivateKey);
  
  displayMedicalRecord(decryptedData);
  
} catch (error) {
  // Transaction failed - access is denied
  console.error('Access verification failed:', error);
  
  // Handle different failure cases
  if (error.message.includes('is_valid')) {
    showError('Invalid or revoked access token');
  } else if (error.message.includes('grant.doctor')) {
    showError('This token is for a different doctor');
  } else if (error.message.includes('grant.record_id')) {
    showError('This token is for a different record');
  } else if (error.message.includes('is_revoked')) {
    showError('Patient has revoked access');
  } else if (error.message.includes('expires_at')) {
    showError('Access token has expired');
  } else {
    showError('Access denied: Unknown reason');
  }
}

Complete Doctor Workflow

// 1. Scan patient's QR code
async function verifyAndAccessRecord() {
  // Scan QR code from patient's device
  const qrData = await scanQRCode();
  
  const accessData = JSON.parse(qrData);
  const {
    token,
    recordId,
    patient,
    recordType,
    estimatedExpiry
  } = accessData;

  // Display access request to doctor for confirmation
  await confirmAccessRequest({
    patientAddress: patient,
    recordType: getRecordTypeName(recordType),
    expiresAt: estimatedExpiry
  });

  try {
    // 2. Verify access on-chain
    await client.execute(
      'salud_health_records_v6.aleo',
      'verify_access',
      [token, doctorAddress, recordId]
    );

    console.log('✓ Access verified');

    // 3. Fetch the encrypted record
    // Note: In Aleo, private records are not publicly readable
    // The patient must provide the encrypted record data off-chain
    const encryptedRecord = await requestRecordFromPatient({
      token,
      recordId
    });

    // 4. Decrypt and display
    const medicalData = await decryptMedicalRecord(
      encryptedRecord,
      doctorPrivateKey
    );

    // 5. Display in EMR system
    displayInEMR(medicalData);

    // 6. Log access for audit
    await logAccess({
      patient,
      recordId,
      timestamp: new Date(),
      doctorId: doctorAddress
    });

  } catch (error) {
    handleAccessDenied(error);
  }
}

Checking Access Before Verification

You can use get_access_info to check access details without verification:
// Check access grant details (doesn't verify all conditions)
try {
  await client.execute(
    'salud_health_records_v6.aleo',
    'get_access_info',
    [token]
  );
  
  // Token exists, but may still be expired/revoked
  console.log('Token exists in the system');
  
  // Now verify full access
  await verifyAccess(token, doctorAddress, recordId);
  
} catch (error) {
  console.log('Token not found');
}

Error Cases

Invalid Token: Token doesn’t exist or was never created
Error: Assertion failed: is_valid
Wrong Doctor: The doctor address doesn’t match the grant
Error: Assertion failed: assert_eq(grant.doctor, doctor)
This prevents doctors from sharing access tokens.
Wrong Record: The record ID doesn’t match the grant
Error: Assertion failed: assert_eq(grant.record_id, record_id)
This prevents using a token to access a different record.
Revoked Access: Patient has manually revoked the access
Error: Assertion failed: !grant.is_revoked
The patient called revoke_access before the natural expiration.
Expired Access: Current block height exceeds expiration
Error: Assertion failed: block.height <= grant.expires_at
The time-based access has expired. Patient must grant new access.

Verification Timing

// Check current block height vs expiration
async function checkExpirationStatus(
  accessToken: string
): Promise<ExpirationStatus> {
  try {
    // Get current block height
    const currentBlock = await client.getBlockHeight();
    
    // Get access grant info (this would require a custom query)
    const grant = await queryAccessGrant(accessToken);
    
    const blocksRemaining = grant.expires_at - currentBlock;
    const timeRemaining = blocksRemaining * 15; // seconds
    
    return {
      isValid: blocksRemaining > 0 && !grant.is_revoked,
      blocksRemaining,
      timeRemaining,
      expiresAt: grant.expires_at,
      isRevoked: grant.is_revoked
    };
    
  } catch (error) {
    return { isValid: false };
  }
}

// Example usage
const status = await checkExpirationStatus(token);

if (status.timeRemaining < 3600) { // Less than 1 hour
  console.warn('Access expiring soon:', status.timeRemaining, 'seconds');
}

Security Considerations

Three-Factor Verification

The verification requires three matching pieces of information:
  1. Access Token (what) - Proves permission was granted
  2. Doctor Address (who) - Proves identity of accessor
  3. Record ID (which) - Specifies exact record being accessed
All three must match the original grant, preventing:
  • Token sharing between doctors
  • Using a token for different records
  • Unauthorized access attempts

Automatic Expiration

assert(block.height <= grant.expires_at);
Expiration is enforced at the blockchain level:
  • Cannot be bypassed by the doctor
  • Cannot be extended without patient action
  • Automatically enforced by block height comparison

Manual Revocation

assert(!grant.is_revoked);
Patients can revoke access at any time:
  • Immediate effect (no delay)
  • Cannot be reversed by doctor
  • Independent of expiration time

Public Verifiability

The access_grants mapping is public, meaning:
  • Access permissions are transparent
  • Auditors can verify access grants
  • Patients can monitor who has access
  • However, medical data remains private in the record

Performance Considerations

Quick Validity Check

Use access_token_valid mapping for cheap checks:
let is_valid: bool = access_token_valid.get_or_use(access_token, false);
This is faster than reading the full AccessGrant struct.

Caching Strategy

// Cache verification results for short periods
const verificationCache = new Map<string, {
  valid: boolean,
  expiresAt: number,
  cachedAt: number
}>();

async function verifyWithCache(
  token: string,
  doctor: string,
  recordId: string
): Promise<boolean> {
  const cacheKey = `${token}-${doctor}-${recordId}`;
  const cached = verificationCache.get(cacheKey);
  
  // Cache for 5 minutes max
  if (cached && Date.now() - cached.cachedAt < 300000) {
    return cached.valid;
  }
  
  // Perform actual verification
  try {
    await client.execute('verify_access', [token, doctor, recordId]);
    
    verificationCache.set(cacheKey, {
      valid: true,
      expiresAt: Date.now() + 300000,
      cachedAt: Date.now()
    });
    
    return true;
  } catch {
    return false;
  }
}

Best Practices

  1. Verify before every access: Don’t assume a previously valid token is still valid
  2. Handle all error cases: Provide clear user feedback for each failure type
  3. Log access attempts: Maintain audit trails for compliance
  4. Check expiration proactively: Warn users before tokens expire
  5. Never share tokens: Each doctor should scan their own QR code
  6. Implement retry logic: Handle temporary network issues gracefully
  7. Cache carefully: Very short cache times to avoid stale data

Build docs developers (and LLMs) love