infusion_ffi
Languages: 🇧🇷 Português · 🇪🇸 Español
A Flutter plugin for the Infusion cryptographic vault protocol. Provides AEAD encryption, Ed25519 signing, BLAKE3 content identifiers, BIP-39 mnemonic wallets, and capability-based access delegation — all backed by a closed-source Rust core with prebuilt binaries for every platform.
Platform Support
| Platform | Architectures | Binary delivery |
|---|---|---|
| Android | arm64-v8a · armeabi-v7a · x86_64 | Bundled in package (no download) |
| iOS | arm64 · simulator (arm64 / x86_64) | Bundled in package (no download) |
| Web | All modern browsers | Bundled Wasm asset |
| macOS | arm64 · x86_64 (universal) | Downloaded on first use, then cached |
| Linux | x86_64 · arm64 | Downloaded on first use, then cached |
| Windows | x86_64 · arm64 | Downloaded on first use, then cached |
Desktop binaries are fetched from GitHub Releases,
verified with hardcoded SHA-256 checksums, and cached per version under
systemTemp/infusion_ffi/<version>/. Subsequent runs load from cache; no internet required.
Installation
# pubspec.yaml
dependencies:
infusion_ffi: ^1.5.0-dev.1
flutter pub get
No additional setup is required for Android, iOS, or Web.
For desktop, the binary is downloaded automatically on the first call to any InfusionFFI method.
Quick Start
import 'dart:convert';
import 'dart:typed_data';
import 'package:infusion_ffi/infusion_ffi.dart';
Future<void> main() async {
// 1. Generate a new BIP-39 wallet (12 or 24 words)
final phrase = await InfusionFFI.mnemonicGenerate(wordCount: 12);
// 2. Derive vault keys from the mnemonic
final keysJson = await InfusionFFI.mnemonicRestore(phrase);
final keys = jsonDecode(keysJson) as Map<String, dynamic>;
// 3. Open the vault
final vault = await InfusionFFI.create(
encKeyHex: keys['enc_key_hex'] as String,
signSeedHex: keys['sign_seed_hex'] as String,
);
// 4. Encrypt
final plaintext = Uint8List.fromList(utf8.encode('Hello, Infusion!'));
final frame = await vault.seal(data: plaintext, policyId: 0);
// 5. Decrypt
final recovered = await vault.open(frame);
print(utf8.decode(recovered)); // Hello, Infusion!
// 6. Always dispose to release native memory
await vault.dispose();
}
API Reference
Vault Lifecycle
Create a vault from hex-encoded keys. Always call dispose() when done.
final vault = await InfusionFFI.create(
encKeyHex: '...', // 32-byte encryption key, hex-encoded
signSeedHex: '...', // 32-byte Ed25519 seed, hex-encoded
);
// ... use vault ...
await vault.dispose(); // releases native memory; no-op if already disposed
Encrypt & Decrypt
seal encrypts data into a self-describing frame that includes the ciphertext,
AEAD authentication tag, and metadata. open decrypts and authenticates atomically —
it throws if the frame was tampered with.
// Encrypt
final frame = await vault.seal(
data: Uint8List.fromList(utf8.encode('secret data')),
policyId: 0, // 0 = owner-only (default)
aad: optionalAad, // optional additional authenticated data
);
// Decrypt — throws on authentication failure
final plaintext = await vault.open(frame);
// Verify without decrypting
final ok = await vault.verify(frame); // bool
// Detailed result (useful for diagnostics)
final result = await vault.verifyDetailed(frame);
// result.ok → bool
// result.code → int (reason code when !ok)
// result.cid → Uint8List (32-byte BLAKE3 content ID of the frame)
Capability Tokens
Delegate scoped access to other identities without sharing keys. Tokens are signed by the issuer, carry an expiration timestamp, and bind to a specific Ed25519 public key — making them verifiable by any party that holds the public key.
// Hash the resource being protected
final resourceCid = await InfusionFFI.cid(resourceBytes);
// Issue a READ token valid for 24 hours
final token = await vault.issueCap(
scopeCid: resourceCid,
rights: 0x01, // bitmask: 0x01 = READ, 0x02 = WRITE
expTs: DateTime.now()
.add(const Duration(hours: 24))
.millisecondsSinceEpoch ~/ 1000,
delegatedPub32: recipientPub32, // 32-byte Ed25519 public key
);
// Recipient verifies signature, expiration, and identity
final valid = await vault.verifyCap(
capToken: token,
requesterPub32: recipientPub32,
);
// Open a sealed frame using a delegated token
final plaintext = await vault.open(frame, capToken: token);
Key Derivation
Derive deterministic 32-byte sub-keys from the vault seed using a context string. Use this to encrypt local storage, derive per-service keys, or generate stable identifiers — without persisting extra secrets.
// Context string uniquely identifies the derived key's purpose
final context = Uint8List.fromList(utf8.encode('storage:user_box'));
final boxKey = await vault.deriveKey(context); // Uint8List (32 bytes)
// Searchable blind index — deterministic hash of a plaintext term
// Useful for querying encrypted fields without leaking the value
final idx = await vault.blindIndex('user@example.com'); // Uint8List (32 bytes)
BIP-39 Mnemonic Wallet
Generate a human-readable mnemonic and deterministically derive vault keys from it. Store only the mnemonic (ideally in secure storage); re-derive keys on demand.
// Generate a new mnemonic
final phrase12 = await InfusionFFI.mnemonicGenerate(wordCount: 12);
final phrase24 = await InfusionFFI.mnemonicGenerate(wordCount: 24);
// Restore keys from an existing mnemonic
// Returns a JSON string: {"enc_key_hex": "...", "sign_seed_hex": "..."}
final json = await InfusionFFI.mnemonicRestore(phrase12);
final keys = jsonDecode(json) as Map<String, dynamic>;
final vault = await InfusionFFI.create(
encKeyHex: keys['enc_key_hex'] as String,
signSeedHex: keys['sign_seed_hex'] as String,
);
Ed25519 Signatures
Sign arbitrary messages and verify signatures against known public keys.
final seed32 = await InfusionFFI.mnemonicRestore(phrase)
.then((j) => hexDecode((jsonDecode(j) as Map)['sign_seed_hex'] as String));
final message = Uint8List.fromList(utf8.encode('content to sign'));
// Sign
final sig64 = await InfusionFFI.signWithSeed(seed32, message); // 64 bytes
// Derive Ed25519 public key from seed
final pub32 = await InfusionFFI.derivePubkey(seed32); // 32 bytes
// Verify
final valid = await InfusionFFI.verifySignature(
pub32: pub32,
msg: message,
sig64: sig64,
);
Content Identifiers & Hashing
// BLAKE3 content ID — 32-byte stable identifier for any byte sequence
final cid = await InfusionFFI.cid(data); // Uint8List (32 bytes)
// SHA-256
final hashBytes = await InfusionFFI.sha256(data); // Uint8List (32 bytes)
final hashHex = await InfusionFFI.sha256Hex('input'); // lowercase hex String
// sha256Hex is useful for OIDC/Apple Sign-In nonce derivation:
// final hashedNonce = await InfusionFFI.sha256Hex(rawNonce);
Desktop: Binary Download
On macOS, Linux, and Windows, InfusionLoader.load() (called internally on every
InfusionFFI operation) downloads the platform binary from GitHub Releases, verifies
the SHA-256 checksum against a hardcoded value for this version, and caches it.
First run requires network access. Once cached, the plugin works offline.
| Platform | Binary filename |
|---|---|
| macOS (universal) | libinfusion_ffi.dylib |
| Linux x86_64 | libinfusion_ffi-linux-x64.so |
| Linux arm64 | libinfusion_ffi-linux-arm64.so |
| Windows x86_64 | infusion_ffi-windows-x64.dll |
| Windows arm64 | infusion_ffi-windows-arm64.dll |
Testing
# All platforms
flutter test
# Web (validates Wasm loading in a real browser)
flutter test --platform chrome
Widget tests using flutter_test block HttpClient by default. For desktop loader tests,
use integration_test or allow network access. For web, prefer running a real Flutter app
to validate Wasm loading rather than relying on headless test environments.
Security
| Primitive | Algorithm | Notes |
|---|---|---|
| Authenticated encryption | ChaCha20-Poly1305 | Default; AEAD — open() rejects tampered frames |
| Signatures | Ed25519 | High-performance, constant-time, safe curve |
| Content hashing | BLAKE3 | Fast, parallel, cryptographically secure |
| Checksums | SHA-256 | Used for desktop binary verification and OIDC nonces |
| Key derivation | HKDF | Deterministic sub-key derivation from vault seed |
| Mnemonic | BIP-39 | 12 or 24 words — deterministic key restoration |
Secret keys are redacted in debug logs (first 6 + last 4 hex characters only).
License
The Dart/Flutter wrapper is licensed under the MIT License — see LICENSE.
The Infusion core (compiled Rust binaries) is proprietary software — see BINARY_LICENSE. Redistribution of the prebuilt binaries is permitted only as described in that file.