Blockchain Integration
This document describes how Oneliac integrates with the Solana blockchain for proof verification, transaction recording, and oracle event management.
Why Blockchain?
Blockchain provides Oneliac with:
- Immutability: Patient transactions cannot be altered
- Transparency: Audit trail of all operations
- Decentralization: No single point of failure
- Smart Contracts: Automated proof verification
- Oracles: Cross-chain pharmacy data verification
Solana Integration
Oneliac uses Solana and the Anchor framework for smart contracts because of:
- High throughput (65,000+ TPS)
- Low transaction costs
- Fast finality (~400ms)
- Anchor provides safety guarantees
Solana Program Architecture
┌─────────────────────────────────────────────────────────────┐
│ Solana Blockchain (Devnet/Mainnet) │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Healthcare Proof Verifier Program (Anchor) │ │
│ │ │ │
│ │ Instructions: │ │
│ │ - verify_eligibility_proof() │ │
│ │ - verify_prescription_proof() │ │
│ │ - record_transaction() │ │
│ │ - register_ipfs_hash() │ │
│ │ │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ On-Chain State Accounts │ │
│ │ - VerificationRegistry (proof records) │ │
│ │ - IPFSHashRegistry (patient data references) │ │
│ │ - OracleResultCache (pharmacy confirmations) │ │
│ │ - TransactionLog (audit trail) │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Events (Emitted after verification) │ │
│ │ - ProofVerified(proof_id, eligible) │ │
│ │ - TransactionRecorded(tx_id, timestamp) │ │
│ │ - IPFSHashRegistered(cid) │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Smart Contract Components
Proof Verifier Account
#[account]
pub struct ProofVerifier {
/// Verification key for Groth16 proofs
pub verification_key: Vec<u8>,
/// Authority who can update the verifier
pub authority: Pubkey,
/// Version of the verifier (for updates)
pub version: u32,
}
Verification Registry Account
#[account]
pub struct VerificationRegistry {
/// Proof ID (hash of proof content)
pub proof_id: [u8; 32],
/// Timestamp of verification
pub verified_at: i64,
/// Eligibility result
pub eligible: bool,
/// Coverage percentage
pub coverage_pct: u32,
/// Patient commitment hash (for privacy)
pub patient_commitment: [u8; 32],
/// Bump seed for PDA derivation
pub bump: u8,
}
IPFS Hash Registry Account
#[account]
pub struct IPFSHashRegistry {
/// IPFS Content Identifier
pub ipfs_cid: String,
/// SHA256 hash of patient data
pub data_hash: [u8; 32],
/// Timestamp of registration
pub registered_at: i64,
/// Patient commitment (privacy-preserving reference)
pub patient_commitment: [u8; 32],
/// Bump seed
pub bump: u8,
}
Instruction Handlers
verify_eligibility_proof
pub fn verify_eligibility_proof(
ctx: Context<VerifyProof>,
proof: Vec<u8>,
public_inputs: Vec<[u8; 32]>,
ipfs_cid: String,
) -> Result<()> {
// Load verifier
let verifier = &ctx.accounts.proof_verifier;
// Verify proof using Groth16
let proof_valid = verify_groth16_proof(
&proof,
&public_inputs,
&verifier.verification_key
)?;
if !proof_valid {
return Err(ProgramError::InvalidArgument.into());
}
// Create or update registry
let registry = &mut ctx.accounts.verification_registry;
registry.proof_id = hash_proof(&proof);
registry.verified_at = Clock::get()?.unix_timestamp;
registry.eligible = true; // Result from proof
registry.ipfs_cid = ipfs_cid.clone();
// Emit event
emit!(ProofVerifiedEvent {
proof_id: registry.proof_id,
eligible: true,
timestamp: registry.verified_at,
});
Ok(())
}
register_ipfs_hash
pub fn register_ipfs_hash(
ctx: Context<RegisterIPFSHash>,
ipfs_cid: String,
data_hash: [u8; 32],
) -> Result<()> {
let registry = &mut ctx.accounts.ipfs_registry;
registry.ipfs_cid = ipfs_cid.clone();
registry.data_hash = data_hash;
registry.registered_at = Clock::get()?.unix_timestamp;
emit!(IPFSHashRegisteredEvent {
ipfs_cid,
data_hash,
timestamp: registry.registered_at,
});
Ok(())
}
Transaction Flow
Eligibility Verification on-chain
1. Client submits request to Oneliac API
2. API generates ZK proof off-chain
3. API builds Solana transaction:
- Program: Healthcare Proof Verifier
- Instruction: verify_eligibility_proof
- Data: proof, public_inputs, ipfs_cid
4. Transaction signed and submitted to Solana
5. RPC endpoint broadcasts to network
6. Validators execute program
- Load proof verifier account
- Verify Groth16 proof
- Create verification registry account
- Emit ProofVerifiedEvent
7. Transaction finalized (~400ms on Solana)
8. API listens for events or polls account state
9. Returns result to client:
{
"eligible": true,
"coverage_pct": 80,
"zk_proof_verified": true,
"transaction_signature": "..."
}
State Account Derivation (PDAs)
Oneliac uses Program Derived Addresses (PDAs) for deterministic account creation:
// Eligibility verification registry PDA
let (registry_pda, bump) = Pubkey::find_program_address(
&[
b"eligibility_registry",
patient_commitment.as_ref(),
proof_id.as_ref(),
],
&PROGRAM_ID,
);
// IPFS hash registry PDA
let (ipfs_pda, bump) = Pubkey::find_program_address(
&[
b"ipfs_registry",
ipfs_cid.as_bytes(),
],
&PROGRAM_ID,
);
Benefits:
- Accounts are deterministic
- No separate account creation needed
- Reduces transaction overhead
- Predictable address for lookups
Events and Indexing
Emitted Events
#[event]
pub struct ProofVerifiedEvent {
pub proof_id: [u8; 32],
pub eligible: bool,
pub timestamp: i64,
}
#[event]
pub struct TransactionRecordedEvent {
pub tx_id: [u8; 32],
pub operation: String, // "eligibility", "prescription"
pub status: String, // "success", "failed"
pub timestamp: i64,
}
#[event]
pub struct IPFSHashRegisteredEvent {
pub ipfs_cid: String,
pub data_hash: [u8; 32],
pub timestamp: i64,
}
Listening to Events (Client Side)
# Using Solana Web3.py
from solders.rpc.responses import DataSlice
async def listen_for_proof_events():
# Subscribe to program logs
with ws_client.subscription_ctx(
await ws_client.logs_subscribe(
logs_subscribe_filter=RpcTransactionLogsFilter(
mention=PROGRAM_ID
)
)
) as logs:
async for log in logs:
# Parse log to extract ProofVerifiedEvent
if "ProofVerified" in log.value.logs:
# Extract event data
proof_id = parse_event_log(log)
print(f"Proof verified: {proof_id}")
Data Integrity Checks
Verification Sequence
- Proof Verification: Groth16 verifier confirms proof cryptography
- Public Input Validation: Confirm procedure code, insurance ID valid
- IPFS Hash Check: Verify SHA256 hash matches registered hash
- Patient Commitment: Ensure proof matches patient identity (privacy-preserving)
- Timestamp Validation: Reject old proofs (prevent replay)
On-Chain Verification Code
// Validate IPFS hash against registered data
pub fn verify_data_integrity(
ipfs_cid: &str,
data_hash: &[u8; 32],
ipfs_registry: &IPFSHashRegistry,
) -> Result<()> {
if ipfs_registry.ipfs_cid != ipfs_cid {
return Err(ProgramError::InvalidArgument.into());
}
if ipfs_registry.data_hash != *data_hash {
return Err(ProgramError::InvalidArgument.into());
}
Ok(())
}
Fee Structure
Solana transaction costs are paid in Lamports (1 SOL = 1,000,000,000 lamports):
| Operation | Lamports | Cost (approx) |
|---|---|---|
| Proof verification | 5,000 | $0.0015 |
| IPFS hash registration | 2,500 | $0.0008 |
| Account creation (registry) | 890,880 | $0.26 |
These are typical estimates; actual costs vary with network congestion.
Cost Optimization
- PDA Accounts: Created only once per patient-procedure pair
- Batching: Multiple operations in single transaction
- Devnet Testing: Free on testnet before mainnet deployment
- Account Reuse: Update existing accounts instead of creating new ones
Network Selection
Development
Network: Solana Devnet
Endpoint: https://api.devnet.solana.com
RPC Speed: ~400ms finality
Cost: Free (airdrop SOL)
Production
Network: Solana Mainnet-Beta
Endpoint: https://api.mainnet-beta.solana.com
RPC Speed: ~400ms finality
Cost: ~$0.00025-0.001 per transaction
Best Practices
- Key Management: Use hardware wallets for production keys
- Error Handling: Always verify transaction success before returning to client
- Monitoring: Track transaction success rates and fees
- Event Indexing: Use Solana indexers (Marinade, Magic Eden API) for event querying
- Idempotency: Design transactions to be idempotent (safe to retry)
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Proof verification fails | Invalid proof or wrong verifier key | Regenerate proof, verify circuit |
| Account too small | Insufficient space allocated | Increase account rent |
| Rate limited by RPC | Too many requests | Use private RPC endpoint or batch requests |
| Transaction expired | Blockhash too old | Use fresh blockhash (< 2 min old) |