infusion_ffi 1.6.0
infusion_ffi: ^1.6.0 copied to clipboard
Flutter plugin for the Infusion crypto vault — AEAD encryption, Ed25519 signing, BLAKE3 CIDs, BIP-39 wallets, and capability tokens. Prebuilt binaries for all platforms.
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.