Skip to content

zcashme/zcash-verification-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

74 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ZVS - Zcash Verification Service

A two-factor authentication (2FA) service using shielded Zcash transactions. Users send verification requests via encrypted transaction memos, and ZVS responds with HMAC-derived one-time passwords (OTPs).

How It Works

  1. User sends verification request - A shielded Zcash transaction with a memo containing the verification payload
  2. ZVS monitors the blockchain - Connects to a lightwalletd server and scans for incoming transactions
  3. OTP generation - For valid verification requests, ZVS generates an HMAC-SHA256 based 6-digit OTP
  4. Response transaction - ZVS sends the OTP back to the user via a shielded transaction memo
┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│   USER                                         ZVS (service wallet)         │
│                                                                             │
│   ┌─────────┐      tx with memo:               ┌─────────┐                  │
│   │         │  "{zvs/1234567890123456,u1...}"  │         │                  │
│   │  Wallet │ ──────────────────────────────►  │  Wallet │                  │
│   │         │                                  │         │                  │
│   └─────────┘                                  └────┬────┘                  │
│        ▲                                            │                       │
│        │                                            │ 1. Decrypt memo       │
│        │                                            │ 2. Parse session ID   │
│        │                                            │ 3. HMAC(secret, session)│
│        │                                            │ 4. Generate OTP       │
│        │                                            ▼                       │
│        │                                       ┌─────────┐                  │
│        │       tx with memo: "847291"          │  Build  │                  │
│        │       (50,000 zats)                   │   tx    │                  │
│        └────────────────────────────────────── │         │                  │
│                                                └─────────┘                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Requirements

  • Rust toolchain (edition 2021)
  • Access to a lightwalletd server

Configuration

ZVS reads secrets from zvs_data/keys.toml:

mnemonic = "word1 word2 ... word24"
otp_secret = "a71440d829e0403019d195a78afd89efe4c18a4e"
birthday_height = 3150000
Field Description
mnemonic 24-word BIP39 mnemonic phrase
otp_secret Secret key for HMAC-based OTP generation (hex-encoded)
birthday_height Block height when the wallet was created

Set RUST_LOG=debug to increase log verbosity (default: info).

Building

cargo build --release

Running

cargo run --release

On startup, ZVS will:

  • Perform an initial sync to chain tip
  • Display the wallet's unified address and balance
  • Process any pending verification requests found during sync
  • Enter the monitoring loop

Architecture

The main loop exclusively owns the wallet. The mempool monitor runs in a separate task with read-only access (UFVK decryption only) and sends detected requests back via an mpsc channel.

┌─────────────────────────┐                          ┌─────────────────────────┐
│   MEMPOOL MONITOR       │                          │   MAIN LOOP             │
│   (spawned task)        │                          │   (owns wallet)         │
│                         │     mpsc channel         │                         │
│ • Streams mempool txs   │────────────────────────► │ • Periodic block sync   │
│ • Decrypts memos (UFVK) │    VerificationRequest   │ • Enhances transactions │
│ • Validates requests    │                          │ • Sends OTP responses   │
│ • No wallet mutation    │                          │ • Deduplicates via      │
│                         │                          │   HashSet<TxId>         │
└─────────────────────────┘                          └─────────────────────────┘
  • Mempool Monitor: Streams unconfirmed transactions in real-time, decrypts memos with the UFVK, and forwards valid requests over the channel.
  • Main Loop: Periodically syncs the wallet to chain tip (every 30s), receives mempool requests from the channel, and sends OTP responses. All wallet mutation happens here.
  • Deduplication: An in-memory HashSet<TxId> prevents duplicate OTP responses across both the mempool and sync paths.

Source Files

src/
├── main.rs       # Entry point, keys.toml loading, event loop
├── wallet.rs     # Local wallet operations (keys, DB, proving, signing)
├── sync.rs       # Block sync with lightwalletd, in-memory block cache
├── mempool.rs    # Real-time mempool streaming and memo decryption
├── memo_rules.rs # Memo format validation and parsing
└── otp_rules.rs  # OTP generation, transaction building, and broadcast

Core Components

  • Wallet - Local-only wallet: keys, database, proving, signing (no network I/O)
  • MemBlockCache - In-memory cache for compact blocks during sync
  • DecryptedMemo - Decrypted memo with txid and value from a received transaction
  • VerificationRequest - Validated request ready for OTP generation

Protocol

Verification Request

Send a shielded transaction to the ZVS wallet address with:

  • Memo format: (DO NOT MODIFY){zvs/session_id,u-address}
  • Minimum payment: 200,000 zatoshis (0.002 ZEC)

Verification Response

ZVS responds with a shielded transaction containing:

  • Memo: 6-digit OTP code (e.g., 847291)
  • Amount: 50,000 zatoshis (0.0005 ZEC)

Memo Format

The memo payload is extracted from between the first { and }:

zvs/session_id,u-address
  • zvs/ - Prefix identifying a verification request (lowercase)
  • session_id - Exactly 16 ASCII digits for OTP entropy
  • u-address - User's unified address for OTP response

Example memo:

(DO NOT MODIFY){zvs/1234567890123456,u1abc123...}

Web App Integration

Web applications can independently verify OTPs by sharing the otp_secret with ZVS:

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│    Web App      │     │   User Wallet   │     │      ZVS        │
│(has otp_secret) │     │                 │     │(has otp_secret) │
└────────┬────────┘     └────────┬────────┘     └────────┬────────┘
         │                       │                       │
    1. Generate                  │                       │
       session_id                │                       │
         │                       │                       │
    2. Show memo ───────────────►│                       │
       to user                   │                       │
         │                  3. Send tx ─────────────────►│
         │                     with memo                 │
         │                       │                  4. Parse memo
         │                       │                     Generate OTP
         │                       │◄──────────────────────│
         │                       │  5. Send OTP in tx    │
         │                       │                       │
    6. User enters  ◄────────────│                       │
       OTP from wallet           │                       │
         │                       │                       │
    7. Web app computes          │                       │
       HMAC(secret, session_id)  │                       │
       and verifies match        │                       │

Browser OTP Generation

async function generateOTP(secretHex, sessionId) {
  const encoder = new TextEncoder();
  const secretBytes = hexToBytes(secretHex);
  const key = await crypto.subtle.importKey(
    'raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
  );
  const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(sessionId));
  const bytes = new Uint8Array(signature);
  const code = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
  return String(code % 1000000).padStart(6, '0');
}

function hexToBytes(hex) {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < hex.length; i += 2) {
    bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
  }
  return bytes;
}

Security Considerations

  • Shielded transactions - All communication uses Zcash shielded pools (Sapling/Orchard)
  • HMAC-SHA256 - OTPs are derived deterministically from session IDs
  • No shared wallet state - The mempool task only decrypts; all spending is serialized in the main loop

Dependencies

Key dependencies:

  • zcash_client_backend / zcash_client_sqlite - Zcash wallet functionality
  • zcash_proofs - Zero-knowledge proof generation
  • tonic - gRPC client for lightwalletd
  • hmac / sha2 - OTP generation
  • tokio - Async runtime

License

MIT

About

No description, website, or topics provided.

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages