infusion_ffi

pub.dev Build License: MIT

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.