Shielded Memo Support
Feature: Inbound Shielded Memo for ZEC Deposits
Background
Currently, MAYAChain requires users to include memo data in Zcash transparent (t-address) transactions using OP_RETURN outputs. This approach has several limitations:
- OP_RETURN outputs have size constraints (80 bytes standard)
- Transparent transactions don't utilize Zcash's privacy features
- Users sending from shielded addresses must first deshield to transparent addresses
Motivation
Zcash's shielded protocols (Sapling and Orchard) support encrypted memo fields (512 bytes) that can carry arbitrary data. By leveraging this capability, we can:
- Improve wallet compatibility: Most Zcash wallets don't support OP_RETURN out-of-the-box as it's non-standard in Zcash and not widely adopted
- Avoid OP_RETURN constraints on transparent transactions (80 bytes standard limit)
- Improve UX for privacy-conscious users by using Zcash's intended memo mechanism
- Provide atomic memo+value in a single transaction
Proposed Solution
Create a centrally-managed shielded wallet that accepts memo-bearing deposits. Users send a single transaction with two outputs: one transparent (for value) and one shielded (for memo).
Bifrost Processing Flow
- Detect transaction with output to vault t-address
- Extract value: 1.5 ZEC from transparent output
- Trial decrypt shielded outputs using corresponding IVK (sapling/orchard)
- Extract memo: "SWAP:MAYA.CACAO:maya1recipient..."
- Create inbound observation:
- TxID: abc123...
- From: (same as current flow)
- To: t1VaultAddress...
- Amount: 1.5 ZEC
- Memo: "SWAP:MAYA.CACAO:maya1recipient..."
User Flow
Transaction Structure Example
Transaction: abc123...
- Inputs:
- User's shielded/transparent address
- Transparent Outputs:
- Output 0: 1.5 ZEC # t1VaultAddress... (MAYAChain vault)
- Output 1: 0.01 ZEC # t1Change... (user's change)
- Shielded Outputs (Sapling):
- Output 0: 0 ZEC → u1ShieldedMemo...
- Encrypted Memo: "SWAP:MAYA.CACAO:maya1recipient..."
Flow
- User queries /inbound_addresses (or /constants) endpoint
- Receives:
- Standard t-address (for value transfer)
- Unified Address (UA) with Sapling + Orchard components
- Incoming Viewing Key (IVK) for memo decryption
- User sends ONE transaction with minimum TWO outputs to Maya: a. Transparent output to t-address with actual value b. Shielded output (Sapling or Orchard) to UA with value=0 and memo
- Bifrost scans the transaction:
- Reads transparent output for value
- Decrypts shielded output using IVK to extract memo
- Processes as single inbound observation
Architecture
Implementation Components
1. Wallet Creation
- Generate a single HD wallet for shielded memo reception
- Derive Unified Address containing only:
- Sapling receiver
- Orchard receiver
- No transparent component
- Store seed/keys in secure configuration (similar to TSS vault approach)
2. API Changes (/inbound_addresses)
Current Response:
{
"chain": "ZEC",
"pub_key": "...",
"address": "t1...",
"router": null,
"halted": false,
"gas_rate": "10"
}
Proposed Enhancement:
{
"chain": "ZEC",
"pub_key": "...",
"address": "t1...",
"router": null,
"halted": false,
"gas_rate": "10",
"shielded_memo": {
"unified_address": "u1...",
"ivk_sapling": "zivk...",
"ivk_orchard": "...",
"enabled": true
}
}
3. Bifrost Scanner Changes
Transaction Scanning:
- Monitor ZEC chain for transactions containing:
- Transparent output to vault t-address
- T-output with OP_RETURN or Shielded output (detectable via IVK trial decryption)
- For each scanned transaction:
- Extract value from transparent output
- Attempt to decrypt shielded outputs using provided IVK
- If decryption succeeds, extract memo from shielded note
- Create single inbound observation with value + memo
1. Key Management
- IVK exposure allows viewing incoming transactions but NOT spending
- Seed/spending keys must be secured (HSM, encrypted config, or similar to TSS vault security)
- Consider key rotation strategy (requires new UA publication)
2. DOS Protection
- IVK trial decryption is computationally expensive
- Limit trial decryption attempts per block
- Cache decryption results to avoid re-processing
- Consider skipping trial decryption if transaction has valid OP_RETURN memo
4. Privacy Implications
- IVK publication allows anyone to view incoming shielded transactions to this address
- This is acceptable as the protocol endpoint is public anyway
- Users' outgoing addresses remain private (sender privacy preserved)
- Memo contents are visible to anyone with IVK (same as OP_RETURN)
5. Value in Shielded Output
- Shielded output should have value=0 (only memo)
- If value > 0 is sent to shielded address, funds are NOT spendable (no spending key)
- Document clearly: "Send value to t-address, memo to shielded address"
- Log warning if shielded output value > 0
Implementation Phases
Phase 1: Infrastructure
-
Generate and securely store shielded wallet keys -
Implement IVK-based transaction decryption in Bifrost -
Add shielded memo fields to /inbound_addressesendpoint -
Update ZEC client library with shielded output trial decryption
Phase 2: Transaction Processing
-
Implement dual-output transaction scanning -
Add IVK trial decryption to scanner loop -
Implement memo extraction from decrypted shielded notes -
Update inbound observer to prioritize OP_RETURN over shielded memo
Phase 3: Testing & Validation
-
Add simulation tests for: - Normal dual-output deposit (t-output + shielded memo)
- Shielded memo-only (no t-output) - should reject
- Both shielded memo AND OP_RETURN - prefer OP_RETURN (and don't decrypt?)
- Invalid/malformed memo in shielded output
- Value > 0 in shielded output - log warning/discord automation?
-
Performance tests: Trial decryption overhead
Phase 4: Rollout
-
Deploy to stagenet -
Monitor decryption performance and success rate -
Update documentation and user guides -
Deploy to mainnet via mimir flag (e.g., ZECShieldedMemoEnabled)
Configuration
Constants/Mimir Values
ZECShieldedMemoEnabled: 0/1 # Feature flag
ZECShieldedMemoTrialDecryption: 1 # Enable trial decryption (expensive)
ZECMaxTrialDecryptionsPerBlock: 100 # Limit decryption attempts
Hard-coded Values
// In querier.go
const (
ShieldedMemoUA = "u1..." // Unified Address
SaplingIVK = "zivk..." // Sapling Incoming Viewing Key
OrchardIVK = "..." // Orchard Incoming Viewing Key
)
References
- ZIP 316 - Unified Addresses
- ZIP 302 - Standardized Memo Format
- ZIP 212 - Allow Recipient to Derive Sapling Ephemeral Secret
- Zcash Documentation: Incoming Viewing Keys
- librustzcash: Trial Decryption APIs