Overview
The grant_access transition allows patients to securely share a specific medical record with a healthcare provider. It generates a cryptographic access token with automatic expiration based on block height. The access token can be encoded in a QR code for the doctor to scan and verify.
Function Signature
async transition grant_access(
medical_record: MedicalRecord,
doctor: address,
duration_blocks: u32,
nonce: field
) -> (MedicalRecord, field, Future)
Source: main.leo:262-303
Parameters
The private medical record to share. Must be owned by the caller (enforced by Aleo’s record ownership system). This record is returned unchanged after granting access.
The Aleo address of the healthcare provider receiving access. Cannot be the same as the patient’s address (self-grant prevention).
How long the access should last, measured in blocks. The function automatically clamps this value to safe bounds:
- Minimum: 240 blocks (~1 hour)
- Maximum: 40,320 blocks (~7 days)
- Default recommendation: 5,760 blocks (~24 hours)
Aleo produces ~1 block per 15 seconds = 4 blocks/minute.
Client-provided random nonce for unique access token generation. Ensures the token is cryptographically unpredictable and unique even for repeated grants to the same doctor.
Returns
The original medical record returned to the patient, unchanged. The patient retains full ownership and can grant access to multiple doctors.
Cryptographically generated access token (BHP256 hash of record_id + doctor + patient + nonce). This token should be encoded in a QR code and shared with the doctor. The doctor uses this token to verify their access.
Async finalization that:
- Calculates expiration block height (
block.height + duration_blocks)
- Creates an
AccessGrant struct with all permission details
- Stores the grant in
access_grants mapping (access_token → AccessGrant)
- Marks the token as valid in
access_token_valid mapping
Validation Rules
Self-Grant Prevention
assert_neq(self.caller, doctor);
Patients cannot grant access to themselves. The transaction fails if the doctor address equals the caller’s address.
Duration Clamping
let clamped_min: u32 = duration_blocks < MIN_ACCESS_DURATION_BLOCKS
? MIN_ACCESS_DURATION_BLOCKS
: duration_blocks;
let safe_duration: u32 = clamped_min > MAX_ACCESS_DURATION_BLOCKS
? MAX_ACCESS_DURATION_BLOCKS
: clamped_min;
The function automatically adjusts invalid durations:
- If
duration_blocks < 240, it’s increased to 240 (minimum 1 hour)
- If
duration_blocks > 40320, it’s reduced to 40320 (maximum 7 days)
Record Ownership
The caller must own the medical_record. This is enforced automatically by Aleo’s record system - if you don’t own the record, you can’t pass it to the function.
Access Token Generation
let access_token: field = BHP256::hash_to_field(AccessTokenInput {
record_id: medical_record.record_id,
doctor: doctor,
patient: self.caller,
nonce: nonce,
});
The access token is a cryptographic hash preventing:
- Token prediction (requires knowing the nonce)
- Token reuse across different grants (each nonce creates unique token)
- Unauthorized access (binds to specific doctor + record + patient)
Code Example
import { AleoClient } from '@aleo/sdk';
import QRCode from 'qrcode';
// Initialize Aleo client
const client = new AleoClient();
// Assuming you have a medical record from create_record
const medicalRecord = await getPatientRecord('record_xyz');
// Doctor's Aleo address (scanned from their badge or entered manually)
const doctorAddress = 'aleo1doctor...';
// Grant access for 24 hours (5760 blocks)
const durationBlocks = 5760;
// Generate secure random nonce
const nonce = generateSecureRandom();
try {
const result = await client.execute(
'salud_health_records_v6.aleo',
'grant_access',
[
medicalRecord, // The record to share
doctorAddress, // Doctor's address
durationBlocks, // 5760 blocks (~24 hours)
nonce // Unique random nonce
]
);
const accessToken = result.outputs[1]; // Second return value
const returnedRecord = result.outputs[0]; // First return value
console.log('Access granted!');
console.log('Access token:', accessToken);
// Generate QR code for doctor to scan
const qrData = {
token: accessToken,
recordId: medicalRecord.record_id,
patient: client.address,
expiresInBlocks: durationBlocks
};
const qrCodeUrl = await QRCode.toDataURL(JSON.stringify(qrData));
// Display QR code to patient
displayQRCode(qrCodeUrl);
// Notify doctor (optional, via secure channel)
await notifyDoctor(doctorAddress, {
patientName: "John Doe",
recordType: "Laboratory Results",
expiresAt: calculateExpirationTime(durationBlocks)
});
} catch (error) {
if (error.message.includes('assert_neq')) {
console.error('Cannot grant access to yourself');
} else {
console.error('Failed to grant access:', error);
}
}
Duration Examples
// Quick consultation: 1 hour (240 blocks minimum)
const quickVisit = 240;
// Standard appointment: 24 hours (recommended)
const standardAccess = 5760;
// Extended care: 3 days
const extendedCare = 17280;
// Maximum allowed: 7 days
const maxAccess = 40320;
// Too short: Will be clamped to 240
const tooShort = 100; // → becomes 240
// Too long: Will be clamped to 40320
const tooLong = 100000; // → becomes 40320
Block Time Calculations
// Convert time to blocks (assuming 15 seconds per block)
function timeToBlocks(hours: number): number {
const blocksPerHour = 240; // 60 min * 4 blocks/min
return hours * blocksPerHour;
}
// Examples:
timeToBlocks(1) // 240 blocks = 1 hour
timeToBlocks(6) // 1440 blocks = 6 hours
timeToBlocks(24) // 5760 blocks = 24 hours
timeToBlocks(72) // 17280 blocks = 3 days
timeToBlocks(168) // 40320 blocks = 7 days
// Get approximate expiration time
function getExpirationTime(durationBlocks: number): Date {
const secondsPerBlock = 15;
const now = new Date();
return new Date(now.getTime() + (durationBlocks * secondsPerBlock * 1000));
}
Client-Side Pre-computation
You can compute the access token before calling grant_access using the compute_access_token helper:
// Pre-compute the access token
const predictedToken = await client.execute(
'salud_health_records_v6.aleo',
'compute_access_token',
[
recordId,
doctorAddress,
patientAddress,
nonce
]
);
console.log('Token will be:', predictedToken);
// Generate QR code before actual transaction
const qrCode = await generateQRCode(predictedToken);
// Now execute the actual grant
const result = await client.execute(
'salud_health_records_v6.aleo',
'grant_access',
[record, doctorAddress, duration, nonce] // Same nonce!
);
// result.outputs[1] === predictedToken
On-Chain State Changes
AccessGrant Creation
The finalize function creates and stores the access grant:
let expires_at: u32 = block.height + duration_blocks;
let grant: AccessGrant = AccessGrant {
patient: patient,
doctor: doctor,
record_id: record_id,
access_token: access_token,
granted_at: block.height,
expires_at: expires_at,
is_revoked: false,
};
access_grants.set(access_token, grant);
access_token_valid.set(access_token, true);
This creates two mapping entries:
- Full grant details in
access_grants for verification
- Quick validity flag in
access_token_valid for cheap checks
Error Cases
Self-Grant Attempt: Transaction fails if patient tries to grant access to themselvesError: Assertion failed: assert_neq(self.caller, doctor)
Invalid Record Ownership: Transaction fails if caller doesn’t own the medical recordError: Record ownership violation
Duplicate Access Grant: Creating multiple grants to the same doctor with different nonces will create separate access tokens. Each token is independent and can be revoked separately.
Security Considerations
Token Uniqueness
Each access grant generates a unique token, even for the same patient-doctor-record combination, because of the nonce. This means:
- You can grant access multiple times to the same doctor
- Each grant has independent expiration
- Revoking one token doesn’t affect others
Privacy Guarantees
- Medical data remains private: Only the access grant metadata is public, not the actual medical record
- Doctor verification required: The token alone isn’t enough - doctor must prove their address matches
- Automatic expiration: Access automatically becomes invalid after the expiration block height
Attack Prevention
- No token prediction: Requires knowing the secret nonce
- No replay attacks: Each token is unique and single-use in practice
- No unauthorized doctors: Token binds to specific doctor address
- No stale access: Block height expiration enforced on-chain
QR Code Integration
import QRCode from 'qrcode';
// Recommended QR code data structure
interface AccessQRData {
version: string; // Protocol version (e.g., "1.0")
network: string; // "testnet" or "mainnet"
token: string; // The access token
recordId: string; // Record identifier
patient: string; // Patient address
recordType: number; // Type of medical record (1-10)
grantedAt: number; // Block height when granted
expiresAt: number; // Block height when expires
estimatedExpiry: string; // Human-readable time
}
async function generateAccessQR(
accessToken: string,
record: MedicalRecord,
durationBlocks: number
): Promise<string> {
const currentBlock = await client.getBlockHeight();
const qrData: AccessQRData = {
version: "1.0",
network: "testnet",
token: accessToken,
recordId: record.record_id,
patient: record.owner,
recordType: record.record_type,
grantedAt: currentBlock,
expiresAt: currentBlock + durationBlocks,
estimatedExpiry: getExpirationTime(durationBlocks).toISOString()
};
// Generate QR code with error correction
return await QRCode.toDataURL(JSON.stringify(qrData), {
errorCorrectionLevel: 'H',
width: 300,
margin: 2
});
}
Best Practices
- Use appropriate durations: 24 hours (5760 blocks) for most cases, shorter for quick consultations
- Generate unique nonces: Always use cryptographically secure random values
- QR code delivery: Display QR code on patient’s device for in-person scanning
- Track active grants: Maintain a client-side list of active access grants for easy revocation
- Notify doctors: Send secure notifications when access is granted (optional)
- Implement revocation UI: Make it easy for patients to revoke access if needed
- Test expiration: Ensure your app handles expired tokens gracefully