Skip to content

Viewing Keys

Viewing keys enable selective disclosure of shielded transaction details to authorized parties without compromising overall privacy.

Use CaseDescription
Tax ComplianceShare transaction history with tax authority
AuditAllow auditor to verify specific transactions
InstitutionalMeet regulatory requirements
Dispute ResolutionProve transaction occurred to third party
InheritanceGrant read access to estate executor
Full Privacy (Shielded): Only user sees transactions
Selective Disclosure: User + authorized viewers see specific txs
Compliant Mode: User + designated auditor see all txs
User Seed
└── Master Viewing Key (MVK)
├── Full Viewing Key (FVK)
│ └── Sees ALL transactions
├── Auditor Key (AK)
│ └── Sees ALL txs, time-limited
└── Transaction Viewing Keys (TVK)
└── Sees SINGLE transaction

Root key derived from user’s wallet seed:

const mvk = generateViewingKey('/m/44/501/0')

Grants access to ALL transactions:

const fvk = deriveViewingKey(mvk, '/full')

Warning: FVK exposure reveals entire transaction history.

Time-limited viewing key for compliance:

const auditKey = deriveViewingKey(mvk, '/audit/2024')

Per-transaction key for selective disclosure:

const tvk = deriveViewingKey(mvk, `/tx/${intentHash}`)

Reveals ONLY the specific transaction.

ComponentAlgorithm
Key derivationHKDF-SHA256
Symmetric encryptionXChaCha20-Poly1305
Nonce24 bytes random
import { encryptForViewing, decryptWithViewing } from '@sip-protocol/sdk'
const txData = {
sender: '0xabc...',
recipient: '0xdef...',
amount: 1000n,
asset: 'ETH'
}
// Encrypt for viewing key holder
const encrypted = encryptForViewing(txData, viewingKey)
// Decrypt with key
const decrypted = decryptWithViewing(encrypted, viewingKey)

Encrypt for multiple authorized viewers:

const encrypted = encryptForViewing(txData, [
auditorKey,
complianceKey,
taxKey
])

Each key holder can decrypt independently.

A ViewingProof demonstrates that decrypted data is authentic without revealing the viewing key.

interface ViewingProof {
intentHash: string
revealedData: TransactionData
commitmentProof: {
inputAmountProof: ZKProof
outputAmountProof: ZKProof
}
viewingKeyHash: string
proofTimestamp: number
}

Verifier checks:

  1. ZK proof is valid
  2. Revealed amounts match on-chain commitments
  3. Viewing key hash matches authorized viewer
interface ViewingKeyGrant {
grantId: string
recipientId: string
keyType: 'full' | 'auditor' | 'transaction'
scope: {
startTime?: number
endTime?: number
intentHashes?: string[]
}
createdAt: number
revokedAt?: number
}

Keys can be revoked but previously-viewed data cannot be “un-revealed”:

await revokeViewingKey(grantId)

Important: Revocation prevents future access. Past disclosures are permanent.

PropertyGuarantee
ConfidentialityOnly key holders can decrypt
IntegrityAEAD prevents tampering
AuthenticityViewingProof proves data is real
Selective DisclosureTVK reveals only one transaction
Forward SecrecyKey compromise doesn’t reveal past txs (with TVK)
  • Decrypt transaction details
  • Prove transaction authenticity
  • Verify amounts match commitments
  • Spend funds
  • Create new transactions
  • Modify transaction history
  • Reveal other users’ transactions
LevelViewing Key Behavior
TRANSPARENTNo encryption, public
SHIELDEDEncrypted, no viewing key shared
COMPLIANTEncrypted, auditor key pre-shared
1. User sets privacy_level = COMPLIANT
2. User pre-shares auditor_key with designated auditor
3. For each transaction:
a. Encrypt tx_data with auditor_key
b. Store encrypted blob with intent
c. Auditor can decrypt at any time
4. If audit requested:
a. Auditor decrypts all transactions
b. Auditor generates ViewingProofs for report
1. User has shielded transaction
2. Dispute arises, need to prove transaction
3. User derives TVK for specific intent
4. User shares TVK with arbiter
5. Arbiter decrypts ONLY that transaction
6. Arbiter generates ViewingProof
7. Dispute resolved with cryptographic proof
import {
generateViewingKey,
deriveViewingKey,
encryptForViewing,
decryptWithViewing
} from '@sip-protocol/sdk'
// Generate master key from path
const masterKey = generateViewingKey('/m/44/501/0')
// Derive purpose-specific keys
const auditKey = deriveViewingKey(masterKey, '/audit/2024')
const taxKey = deriveViewingKey(masterKey, '/tax/quarterly')
// Encrypt transaction for auditor
const txData = {
intentHash: '0x123...',
inputAmount: 1000n,
outputAmount: 950n,
sender: '0xabc...',
recipient: '0xdef...'
}
const encrypted = encryptForViewing(txData, auditKey)
// Auditor decrypts
const revealed = decryptWithViewing(encrypted, auditKey)
console.log(revealed.inputAmount) // 1000n
Key TypeTrust RequiredRisk
FVKHighFull history exposure
AKMediumBounded by time
TVKLowSingle transaction
FeatureZcashSIP
Viewing key conceptYesYes
Time-limited keysNoYes
ViewingProofNoYes
Multi-key encryptionNoYes
Intent integrationNoYes