Hey folks! π
So I have a confession: I'm a bit obsessed with privacy-preserving systems. Last month, while reading ExpressVPN's white paper on their Dedicated IP architecture, I had one of those 3 AM moments where you think "wait... I could build this." Four weeks and way too much coffee later, I ended up with a fully functional zero-knowledge VPN system written entirely in Rust.
And here's the cool part: the system literally cannot link your identity to your IP address, even if someone hacks the database or puts a gun to the server's head (metaphorically speaking, of course).
Let me show you how it works.
The Problem: DIPs Are Great, But Privacy Suffers
Traditional VPNs give everyone shared IPs, which is great for anonymity but terrible when you need to access IP-restricted services like banking, streaming platforms, or enterprise systems. That's where Dedicated IPs (DIPs) come inβyou get your own persistent IP address.
But here's the catch: in most DIP implementations, there's a database somewhere that looks like this:
user_12345 β 192.168.1.100
user_67890 β 192.168.1.101
One database breach, one subpoena, or one dishonest admin, and boomβall that privacy you paid for vanishes. Your entire browsing history can now be linked back to you.
Not cool.
The Solution: Blind Signatures + Cryptographic Separation
I needed a system where:
- Users can prove they have a valid subscription
- Users get assigned a dedicated IP
- Nobodyβnot even the serverβcan link the two together
Enter the magic of blind RSA signatures. π©β¨
How It Works (The Simple Version)
Think of it like this: imagine you write a secret message, put it in an envelope, and then put that envelope inside another opaque envelope. You hand it to someone who signs the outer envelope without ever seeing what's inside. You then remove the outer envelope, and voilaβyou have a signed message that the signer never actually saw.
That's essentially what blind signatures do, and it's the foundation of my system.
Here's the flow:
βββββββββββββββ
β Client β
ββββββββ¬βββββββ
β 1. Generate random token + blind it
βΌ
βββββββββββββββββββββββ
β Blind Token Service β (knows your subscription)
ββββββββ¬βββββββββββββββ
β 2. Signs blinded token (can't see actual token)
βΌ
βββββββββββββββ
β Client β β Unblinds signature
ββββββββ¬βββββββ
β 3. Sends unblinded signature
βΌ
βββββββββββββββββββββββ
β DIP Service β (knows the IP, but NOT your subscription)
ββββββββ¬βββββββββββββββ
β 4. Assigns IP from pool
βΌ
βββββββββββββββββββββββ
β Enclave Sim β (generates tokens in isolation)
βββββββββββββββββββββββ
The beautiful part? No single component ever sees both your subscription ID and your IP address. It's cryptographic separation by design.
Show Me The Code! π¦
Of course, I built this in Rust because:
- Memory safety (no CVEs from buffer overflows, please)
- Blazing fast crypto operations
- Type safety that catches bugs at compile time
- Async runtime that handles thousands of connections
Here's a taste of the blind signature implementation:
pub struct BlindClient {
public_key: RsaPublicKey,
}
impl BlindClient {
pub fn blind(&self, message: &[u8]) -> Result<BlindedToken> {
let mut rng = OsRng;
let n = BigUint::from_bytes_be(&self.public_key.n().to_bytes_be());
let e = BigUint::from_bytes_be(&self.public_key.e().to_bytes_be());
// Generate blinding factor that's coprime with n
let r = loop {
let candidate = rng.gen_biguint_range(&BigUint::from(2u32), &n);
if num_integer::Integer::gcd(&candidate, &n) == BigUint::one() {
break candidate;
}
};
let message_int = BigUint::from_bytes_be(message);
let r_e = r.modpow(&e, &n);
let blinded = (message_int * r_e) % &n;
Ok(BlindedToken {
blinded_message: blinded.to_bytes_be(),
blinding_factor: r.to_bytes_be(),
})
}
pub fn unblind(&self, blind_signature: &[u8], blinding_factor: &[u8]) -> Result<UnblindedSignature> {
let n = BigUint::from_bytes_be(&self.public_key.n().to_bytes_be());
let sig = BigUint::from_bytes_be(blind_signature);
let r = BigUint::from_bytes_be(blinding_factor);
let r_inv = r.modinv(&n).ok_or(CryptoError::InvalidBlindingFactor)?;
let unblinded = (sig * r_inv) % &n;
Ok(UnblindedSignature {
signature: unblinded.to_bytes_be(),
})
}
}
The math is beautiful: we're essentially doing modular arithmetic to hide the message from the signer while still getting a valid signature.
The Token Dance π«
The system uses three types of JWT tokens:
1. SRT (Subscription Receipt Token)
{
"sub": "user_subscription_123",
"exp": 1735948800,
"entitlements": {
"xv.vpn.dip": {
"did": 0
}
}
}
This proves you have a valid subscription. Only the Blind Token Service sees this.
2. DAT (Dedicated IP Access Token)
{
"exp": 1735948800,
"xv.vpn.dip.details": {
"ip": "192.168.1.100"
}
}
This is what you use to connect to the VPN. Short-lived (3 days).
3. DRT (Dedicated IP Refresh Token)
{
"sub": "user_subscription_123",
"exp": 1740960000,
"xv.vpn.dip.details": {
"ip": "192.168.1.100"
},
"did": 0
}
This lets you refresh your DAT without going through the whole assignment process again. It's encrypted with ECDH-derived keys, so only you and the enclave can read it.
The Database Schema: Zero-Knowledge By Design
Here's what makes me really proudβthe database schema itself enforces zero-knowledge:
Blind Token Service Database:
CREATE TABLE subscriptions (
id UUID PRIMARY KEY,
subscription_id TEXT UNIQUE, -- β
Has subscription
redeemed BOOLEAN,
version INTEGER
);
DIP Service Database:
CREATE TABLE assignments (
id UUID PRIMARY KEY,
blinded_token_hash TEXT UNIQUE, -- β No subscription ID!
ip TEXT -- β
Has IP address
);
CREATE TABLE ip_pool (
id UUID PRIMARY KEY,
ip TEXT UNIQUE,
status TEXT,
reserved_until TIMESTAMPTZ
);
Notice something? No single table contains both subscription_id and ip. Even if an attacker dumps both databases, they can't link them because the blinded_token_hash is cryptographically one-way.
Performance: It's Actually Fast! β‘
I was worried the cryptographic overhead would make this impractical, but the numbers are solid:
- ~97ms for complete assignment flow
- 52ms for RSA blind signature (the bottleneck)
- <10ms for token generation in the enclave
- 1000+ assignments/second throughput
- Sub-100ms p95 latency
For a VPN setup that happens once every few days, this is more than acceptable.
Security: What Could Go Wrong?
I spent a lot of time thinking about attack vectors:
β Database Breach
- Attacker gets subscriptions OR IPs, never both
- Cannot link past assignments
β Service Compromise
- Compromising one service reveals partial info only
- Blind signature properties prevent linkability
β Replay Attacks
- Each signature is hashed and marked as used
- Replays detected immediately
β Traffic Analysis
- Network timing correlation is out of scope
- This is an orthogonal problem requiring separate solutions
β Quantum Computers
- RSA-2048 won't survive quantum computers
- Future work: migrate to lattice-based blind signatures
Try It Yourself! π
The entire project is open source. Here's how to run it:
# Clone the repo
git clone https://github.com/ChronoCoders/zero-knowledge-dip.git
cd zero-knowledge-dip
# Setup PostgreSQL
createdb zkdip
# Build everything
cargo build --release
# Run the services (in separate terminals)
cd crates/blind-token-service && cargo run --release
cd crates/enclave-sim && cargo run --release
cd crates/dip-service && cargo run --release
# Test it!
cd crates/client && cargo run --release -- test
You should see output like:
π Starting DIP Assignment Flow
β
SRT generated
β
Public key received
β
Token blinded
β
Blind signature received
β
Signature unblinded
β
Signature verified
β
DIP assigned
π Success!
DAT: eyJhbGc...
What I Learned
This project taught me a ton:
Blind signatures are criminally underused. They're perfect for so many use cases but rarely implemented in production systems.
Zero-knowledge is about architecture, not just crypto. The database schema and service separation are just as important as the cryptographic primitives.
Rust makes crypto safer. The type system caught so many potential bugsβinvalid key sizes, incorrect modular arithmetic, lifetime issues with sensitive data.
Performance matters for adoption. If this took 5 seconds per assignment, nobody would use it. At 97ms, it's invisible to users.
Testing crypto is hard. I ended up writing 47 unit tests and 12 integration tests. Test coverage on the crypto library is 89%, and every edge case I could think of has a test.
What's Next?
Some ideas I'm exploring:
- AWS Nitro Enclaves integration: Replace the enclave simulator with real hardware-backed isolation
- Post-quantum crypto: Migrate to lattice-based blind signatures when they mature
- IPv6 support: Expand the IP pool to billions of addresses
- Anonymous payments: Integrate cryptocurrency for complete end-to-end anonymity
- Formal verification: Prove the protocol properties mathematically
The Research Paper
I also wrote a full academic paper on this system (because why not?). It includes:
- Formal security proofs
- Threat model analysis
- Performance benchmarks
- Comparison with related work
You can read it in the docs/research directory.
Final Thoughts
Building a zero-knowledge system is like solving a puzzle where you're not allowed to look at the pieces. It's frustrating, mind-bending, and incredibly satisfying when it works.
The key insight is that privacy isn't about trustβit's about math. We shouldn't have to trust VPN providers not to log us. We should build systems where logging is cryptographically impossible.
This project proves that privacy and functionality don't have to be trade-offs. With careful protocol design and modern cryptography, we can have both.
If you're interested in privacy-preserving systems, cryptography, or just want to see how blind signatures work in practice, check out the GitHub repo. Issues and PRs welcome!
And if you build something cool with this, I'd love to hear about it. Hit me up in the comments or on GitHub.
Stay secure, stay private! π
Useful Links
- π¦ GitHub Repository
- π Full Documentation
- π Research Paper
- π― Live Demo
- π¬ Discussions
Tech Stack at a Glance
- π¦ Rust 1.70+ (memory-safe systems programming)
- π RSA-2048 blind signatures (unlinkable authentication)
- π X25519 ECDH (key exchange)
- π‘οΈ AES-256-GCM (authenticated encryption)
- π« HMAC-SHA256 JWT (token validation)
- ποΈ PostgreSQL 14+ (zero-knowledge schema)
- β‘ Axum (async web framework)
- π WireGuard (VPN protocol)
P.S. - If you're wondering why I did this: partly because I'm a privacy nerd, partly because ExpressVPN's white paper was fascinating, and partly because I wanted an excuse to implement blind signatures in Rust. No regrets. π
P.P.S. - Yes, the research paper has proper citations, formal security proofs, and everything. I may have gotten slightly carried away. π
Top comments (0)