Custody Verification
How to verify your transaction history and prove that every action was properly authorized.
Custody Verification
Every transaction signed through Kairo creates a verifiable record. This guide shows you how to verify your custody trail—proving that every action was properly authorized according to your policy.
What Is the Custody Trail?
Your custody trail is a chain of records stored on Sui blockchain. Each record (called a CustodyEvent) contains:
- What happened — Transaction details, destination, amount
- When it happened — Timestamp from the blockchain
- What authorized it — Reference to the PolicyReceipt
- Proof of integrity — Cryptographic hash linking to previous events
These records are immutable—once created, they can't be changed or deleted.
Why Verification Matters
Verification serves several purposes:
Personal Security
Confirm that:
- No unauthorized transactions occurred
- Every action was policy-approved
- Your wallet history is complete
Compliance
Provide auditors with:
- Verifiable proof of transaction authorization
- Complete activity history
- Cryptographic guarantees of integrity
Dispute Resolution
If questioned about a transaction:
- Prove when it was authorized
- Show which policy version approved it
- Demonstrate the full decision chain
Understanding Custody Events
Each custody event captures:
CustodyEvent
├── chain_id: Reference to your custody chain
├── seq: Sequence number (0, 1, 2, ...)
├── kind: Type of event (transfer, approval, etc.)
├── recorded_at_ms: When recorded on Sui
│
├── prev_hash: Hash of previous event (32 bytes)
├── event_hash: Hash of this event (32 bytes)
│
├── src_namespace: Source chain (1=EVM, 2=BTC, 3=SOL)
├── src_chain_id: Network ID
├── src_tx_hash: Transaction hash on source chain
├── to_addr: Destination address
│
├── policy_object_id: Which policy authorized
├── policy_version: Policy version at time of signing
├── intent_hash: Hash of the unsigned transaction
├── receipt_object_id: The receipt that authorized signing
│
└── payload: Additional application data
How to Verify a Single Event
Step 1: Fetch the Event
Using Kairo's explorer or directly from Sui:
- Go to the Kairo Explorer
- Enter your wallet address
- Find the event you want to verify
- Click to view full details
Or via Sui directly:
https://explorer.sui.io/object/[event_object_id]
Step 2: Verify the Hash
The event_hash is computed from all other fields. To verify:
- Collect all event fields
- Encode them using BCS (Binary Canonical Serialization)
- Compute keccak256 hash
- Compare to stored
event_hash
If they match, the event hasn't been tampered with.
Step 3: Verify Chain Linkage
Check that prev_hash matches the previous event:
- Fetch event with
seq - 1 - Check its
event_hash - Should match this event's
prev_hash
For the first event (seq = 0), prev_hash should be all zeros.
Step 4: Cross-Reference with Source Chain
Verify the transaction actually occurred:
- Take
src_tx_hashfrom the event - Look it up on the source chain's explorer
- Confirm destination, amount, and other details match
Verifying Your Complete Trail
To verify your entire custody history:
Using Kairo Explorer
- Go to Explorer → Your Wallet
- Click Custody Trail
- Click Verify All
- Wait for verification to complete
- Green checkmarks indicate valid events
Manual Full Verification
For complete independence:
Start with seq = 0
├── Verify prev_hash = 0x0000...0000
├── Verify event_hash computation
│
For each subsequent event:
├── Verify prev_hash = previous event_hash
├── Verify event_hash computation
├── Verify receipt_object_id was valid
└── Continue until latest event
Finally:
└── Verify chain's head_hash = last event_hash
Verifying Policy Authorization
Each event references the receipt that authorized it:
Check the Receipt
- Note the
receipt_object_idfrom the event - Since receipts are consumed, you can't fetch them directly
- Instead, check the IntentRecord in the PolicyVault
Verify Intent Record
The PolicyVault stores IntentRecords proving authorization:
- Query the vault for the
intent_hash - The record shows:
- Which receipt authorized it (
receipt_id) - Which policy version was active (
binding_version_id) - When it was recorded (
recorded_at_ms)
- Which receipt authorized it (
This proves the transaction was properly authorized before signing.
Using the Kairo SDK
For programmatic verification:
import { fetchAndVerifyCustodyEvent } from "@kairo/sdk";
// Verify a single event
const result = await fetchAndVerifyCustodyEvent({
suiRpcUrl: "https://fullnode.testnet.sui.io:443",
custodyEventObjectId: "0x...",
});
if (result.ok) {
console.log("Event verified successfully");
} else {
console.error("Verification failed:", result.error);
}
// Verify a complete chain
import { verifyFullCustodyChain } from "@kairo/sdk";
const chainResult = await verifyFullCustodyChain({
suiRpcUrl: "https://fullnode.testnet.sui.io:443",
custodyChainObjectId: "0x...",
});
if (chainResult.valid) {
console.log(`Verified ${chainResult.eventCount} events`);
} else {
console.error("Chain verification failed at seq:", chainResult.failedAt);
}
Creating Audit Reports
For compliance purposes, you can generate verification reports:
From Kairo Explorer
- Navigate to your custody trail
- Select date range
- Click Export Verification Report
- Choose format (PDF, JSON)
- Download includes:
- Event details
- Verification proofs
- Cross-references to source chains
Report Contents
A complete audit report includes:
| Section | Contents | |---------|----------| | Summary | Total events, date range, verification status | | Event List | Each event with full details | | Verification Proofs | Hash computations and chain links | | Policy History | Policy versions active during period | | Source References | Links to transactions on source chains |
What to Do If Verification Fails
If you encounter a verification failure:
Hash Mismatch
The computed hash doesn't match stored hash.
Possible causes:
- Bug in verification code
- Data corruption during fetch
- (Extremely unlikely) Actual tampering
Steps:
- Retry the verification
- Try a different RPC endpoint
- Verify your SDK version is current
- Contact Kairo support if persistent
Chain Break
prev_hash doesn't match previous event's event_hash.
Possible causes:
- Missing event in your query
- Events created out of order (shouldn't happen)
Steps:
- Ensure you're fetching all events
- Check for gaps in sequence numbers
- Report to Kairo if genuine break detected
Receipt Not Found
Can't verify the authorizing receipt.
Explanation: Receipts are consumed after use—this is expected. Use IntentRecords instead:
- Query the PolicyVault for the intent
- The record proves authorization occurred
- This is the correct verification path
Verification Best Practices
Regular Verification
- Verify your trail periodically (monthly for active wallets)
- Set up automated verification for high-value wallets
- Export and archive verification reports
Independent Verification
- Don't rely solely on Kairo's tools
- Use the SDK or manual verification for critical audits
- Cross-reference with source chain explorers
Long-Term Archival
- Keep copies of verification reports
- Archive custody event data independently
- Store proofs that can be verified without Kairo
Technical Details
Hash Algorithm
Events use keccak256 over BCS-encoded data:
hash = keccak256(bcs_encode(EventCanonical))
Canonical Encoding
The EventV2Canonical struct defines what's hashed:
EventV2Canonical {
chain_id: object::ID
seq: u64
kind: u8
recorded_at_ms: u64
prev_hash: vector<u8>
src_namespace: u8
src_chain_id: u64
src_tx_hash: vector<u8>
to_addr: vector<u8>
policy_object_id: object::ID
policy_version: vector<u8>
intent_hash: vector<u8>
receipt_object_id: object::ID
payload: vector<u8>
}
Order matters for BCS encoding—fields must be in this exact order.
Next Steps
- Threat Model — What these protections defend against
- Security Model — How custody fits into overall security
- Glossary — Terms used in custody verification