Privacy Pass protocol implementation for Flutter using Rust FFI. Provides native performance for cryptographic operations.
- ✅ Native Performance: Rust-powered cryptography via FFI
- ✅ Background Processing: Operations run in isolates (via Isolate.spawn) to prevent UI blocking
- ✅ Memory Safe: Automatic memory management with proper FFI bindings
- ✅ Cross-Platform: Supports Android (arm64-v8a, armeabi-v7a, x86_64, x86) and iOS (arm64 + simulators)
- ✅ Easy to Use: Simple Dart API with comprehensive documentation
Privacy Pass is a protocol that allows users to prove they're trustworthy without revealing their identity. It uses cryptographic tokens to provide anonymous authentication, implementing:
- VOPRF (Verifiable Oblivious Pseudorandom Function) using Ristretto255 curve
- RFC 9578 Privacy Pass Issuance Protocol
- Batched token issuance for efficiency
Add to your pubspec.yaml:
dependencies:
privacy_pass_ffi:
path: ../privacy_pass_ffi # Update with your pathThen run:
flutter pub getPrerequisites:
- Rust toolchain (via rustup)
- Android NDK (via Android Studio SDK Manager)
- Xcode (for iOS, macOS only)
- Build tools:
cargo install cargo-ndk cargo-lipo cbindgen
Build for all platforms:
cd path/to/privacypass-lib
bash build_plugin.shThis script will:
- Build Rust libraries for Android (all architectures)
- Build Rust libraries for iOS (device + simulator)
- Generate C headers
- Copy libraries to the Flutter plugin
Use PrivacyPassIsolate to run crypto operations in background isolates:
import 'package:privacy_pass_ffi/privacy_pass_ffi.dart';
// Initialize
final client = PrivacyPassIsolate();
await
client.init
(
concurrency: 2);
try {
// Step 1: Get challenge from origin server
final response = await http.get(Uri.parse('https://origin.com/protected'));
final wwwAuthHeader = response.headers['www-authenticate']!;
// Step 2: Generate token request
final request = await client.generateTokenRequest(
wwwAuthenticateHeader: wwwAuthHeader,
tokenCount: 5,
);
// Step 3: Send to issuer
final issuerResponse = await http.post(
Uri.parse('https://issuer.com/token'),
body: request.tokenRequest,
);
// Step 4: Finalize tokens
final tokens = await client.finalizeTokens(
wwwAuthenticateHeader: wwwAuthHeader,
clientState: request.clientState,
tokenResponse: issuerResponse.body,
);
// Step 5: Use tokens
final protectedResponse = await http.get(
Uri.parse('https://origin.com/protected'),
headers: {'Authorization': 'PrivateToken token=${tokens.first}'},
);
print('Success: ${protectedResponse.body}');
} finally {
await client.dispose();
}Use PrivacyPassClient for synchronous operations:
import 'package:privacy_pass_ffi/privacy_pass_ffi.dart';
final client = PrivacyPassClient();
// Generate token request (blocks current isolate)
final request = client.generateTokenRequest(
wwwAuthenticateHeader: header,
tokenCount: 5,
);
// Finalize tokens
final tokens = client.finalizeTokens(
wwwAuthenticateHeader: header,
clientState: request.clientState,
tokenResponse: serverResponse,
);Background processing client using Dart's built-in Isolate.spawn.
Future<void> init({int concurrency = 2})
- Initialize the client (spawns isolates on-demand)
concurrency: Parameter kept for API compatibility but not used (isolates are created per-request)
Future<TokenRequestResult> generateTokenRequest({required String wwwAuthenticateHeader, required int tokenCount})
- Generate a Privacy Pass token request
- Returns:
TokenRequestResultwithclientStateandtokenRequest
**
Future<List<String>> finalizeTokens({required String wwwAuthenticateHeader, required String clientState, required String tokenResponse})
**
- Finalize tokens from issuer response
- Returns: List of base64-encoded tokens
Future<void> dispose()
- Clean up resources (isolates are automatically terminated after each operation)
Synchronous client (operations block current isolate).
TokenRequestResult generateTokenRequest({required String wwwAuthenticateHeader, required int tokenCount})
- Synchronous token request generation
**
List<String> finalizeTokens({required String wwwAuthenticateHeader, required String clientState, required String tokenResponse})
**
- Synchronous token finalization
String getVersion()
- Get library version string
TokenRequestResult
class TokenRequestResult {
final String clientState; // Preserve for finalization
final String tokenRequest; // Send to issuer
}PrivacyPassException
- Exception thrown when operations fail
privacy_pass_ffi/
├── lib/
│ ├── src/
│ │ ├── bindings.dart # FFI bindings
│ │ ├── privacy_pass_client.dart # Synchronous API
│ │ ├── privacy_pass_isolate.dart # Async isolate API
│ │ └── types.dart # Dart types
│ └── privacy_pass_ffi.dart # Main export
├── android/
│ └── src/main/jniLibs/ # Native .so files
├── ios/
│ └── Frameworks/ # Native .a files
├── scripts/
│ ├── copy_android_libs.sh
│ ├── copy_ios_libs.sh
│ ├── run_example_android.sh
│ └── run_example_ios.sh
└── example/ # Demo app
Android:
cd privacy_pass_ffi
bash scripts/run_example_android.shiOS:
cd privacy_pass_ffi
bash scripts/run_example_ios.shRun unit tests:
cd privacy_pass_ffi
flutter testEnable Rust debug symbols:
In privacypass-lib/src/Cargo.toml:
[profile.release]
debug = trueCheck library loading:
try {
final client = PrivacyPassClient();
print('Library loaded: ${client.getVersion()}');
} catch (e) {
print('Failed to load library: $e');
}┌─────────────────────────────────┐
│ Flutter/Dart Application │
└───────────┬─────────────────────┘
│ dart:ffi
↓
┌─────────────────────────────────┐
│ FFI Bridge (Dart) │
│ - String marshalling │
│ - Memory management │
└───────────┬─────────────────────┘
│ C ABI
↓
┌─────────────────────────────────┐
│ Rust FFI Wrapper │
│ - privacy_pass_token_request │
│ - privacy_pass_token_finalization │
└───────────┬─────────────────────┘
│
↓
┌─────────────────────────────────┐
│ Core Crypto Library (Rust) │
│ - VOPRF / Ristretto255 │
│ - Privacy Pass Protocol │
└─────────────────────────────────┘
Crypto operations are CPU-intensive. Benchmarks on iPhone 14 Pro:
- Token Request (5 tokens): ~15ms
- Token Finalization (5 tokens): ~20ms
Using PrivacyPassIsolate prevents UI jank during these operations.
- Memory Safety: All FFI calls properly manage memory to prevent leaks
- String Lifetime: Input strings are copied; output strings are owned by Rust
- Error Handling: All errors propagate as
PrivacyPassException - Thread Safety: Each isolate has its own client instance (no shared state)
Issue: java.lang.UnsatisfiedLinkError: dlopen failed: library "libkagipp_ffi.so" not found
Solution: Rebuild native libraries and ensure they're copied to jniLibs:
cd privacypass-lib && bash build_plugin.shIssue: Symbol not found: _privacy_pass_token_request
Solution: Clean and rebuild:
cd privacy_pass_ffi/example/ios
rm -rf Pods Podfile.lock
flutter clean
cd ../.. && bash scripts/run_example_ios.shIssue: Rust targets not installed
Solution:
rustup target add aarch64-linux-android armv7-linux-androideabi \
x86_64-linux-android aarch64-apple-ios \
x86_64-apple-ios aarch64-apple-ios-simSee LICENSE file.
This is a Kagi internal project. For issues or questions, contact the Privacy Pass team.