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.
example/lib/main.dart
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:infusion_ffi/infusion_ffi.dart';
void main() {
runApp(const InfusionExampleApp());
}
class InfusionExampleApp extends StatelessWidget {
const InfusionExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'infusion_ffi example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const _ExamplePage(),
);
}
}
class _ExamplePage extends StatefulWidget {
const _ExamplePage();
@override
State<_ExamplePage> createState() => _ExamplePageState();
}
class _ExamplePageState extends State<_ExamplePage> {
final List<_Result> _results = [];
bool _running = false;
Future<void> _run() async {
setState(() {
_results.clear();
_running = true;
});
try {
await _runMnemonic();
await _runSealOpen();
await _runSignVerify();
await _runCapability();
await _runKeyDerivation();
await _runHashing();
} catch (e) {
_add('Error', e.toString(), ok: false);
} finally {
setState(() => _running = false);
}
}
// ── BIP-39 mnemonic ──────────────────────────────────────────────────────
Future<void> _runMnemonic() async {
final phrase = await InfusionFFI.mnemonicGenerate(wordCount: 12);
_add('mnemonicGenerate (12)', phrase);
final json = await InfusionFFI.mnemonicRestore(phrase);
final keys = jsonDecode(json) as Map<String, dynamic>;
_add(
'mnemonicRestore',
'enc_key_hex: ${_short(keys['enc_key_hex'] as String)}\n'
'sign_seed_hex: ${_short(keys['sign_seed_hex'] as String)}',
);
}
// ── Encrypt / decrypt ────────────────────────────────────────────────────
Future<void> _runSealOpen() async {
final phrase = await InfusionFFI.mnemonicGenerate(wordCount: 12);
final keys = jsonDecode(await InfusionFFI.mnemonicRestore(phrase))
as Map<String, dynamic>;
final vault = await InfusionFFI.create(
encKeyHex: keys['enc_key_hex'] as String,
signSeedHex: keys['sign_seed_hex'] as String,
);
try {
const message = 'Hello, Infusion!';
final frame = await vault.seal(
data: Uint8List.fromList(utf8.encode(message)),
policyId: 0,
);
_add('seal', '${frame.length} bytes frame');
final plain = await vault.open(frame);
final recovered = utf8.decode(plain);
_add('open', recovered, ok: recovered == message);
final ok = await vault.verify(frame);
_add('verify', ok.toString(), ok: ok);
final detailed = await vault.verifyDetailed(frame);
_add(
'verifyDetailed',
'ok=${detailed.ok} code=${detailed.code} '
'cid=${_short(_hex(detailed.cid))}',
ok: detailed.ok,
);
} finally {
await vault.dispose();
_add('dispose', 'vault released');
}
}
// ── Ed25519 signatures ───────────────────────────────────────────────────
Future<void> _runSignVerify() async {
final phrase = await InfusionFFI.mnemonicGenerate(wordCount: 12);
final keys = jsonDecode(await InfusionFFI.mnemonicRestore(phrase))
as Map<String, dynamic>;
final seed32 = _fromHex(keys['sign_seed_hex'] as String);
final message = Uint8List.fromList(utf8.encode('signed content'));
final sig64 = await InfusionFFI.signWithSeed(seed32, message);
_add('signWithSeed', '${sig64.length} bytes signature');
final pub32 = await InfusionFFI.derivePubkey(seed32);
_add('derivePubkey', _short(_hex(pub32)));
final valid = await InfusionFFI.verifySignature(
pub32: pub32,
msg: message,
sig64: sig64,
);
_add('verifySignature', valid.toString(), ok: valid);
// Tamper detection
final tampered = Uint8List.fromList(message)..last ^= 0xFF;
final invalid = await InfusionFFI.verifySignature(
pub32: pub32,
msg: tampered,
sig64: sig64,
);
_add('verifySignature (tampered)', 'correctly rejected: ${!invalid}',
ok: !invalid);
}
// ── Capability tokens ────────────────────────────────────────────────────
Future<void> _runCapability() async {
final phrase = await InfusionFFI.mnemonicGenerate(wordCount: 12);
final keys = jsonDecode(await InfusionFFI.mnemonicRestore(phrase))
as Map<String, dynamic>;
final vault = await InfusionFFI.create(
encKeyHex: keys['enc_key_hex'] as String,
signSeedHex: keys['sign_seed_hex'] as String,
);
try {
final recipientPhrase = await InfusionFFI.mnemonicGenerate(wordCount: 12);
final recipientKeys =
jsonDecode(await InfusionFFI.mnemonicRestore(recipientPhrase))
as Map<String, dynamic>;
final recipientPub32 = await InfusionFFI.derivePubkey(
_fromHex(recipientKeys['sign_seed_hex'] as String),
);
final resourceCid = await InfusionFFI.cid(
Uint8List.fromList(utf8.encode('resource data')),
);
final token = await vault.issueCap(
scopeCid: resourceCid,
rights: 0x01, // READ
expTs: DateTime.now()
.add(const Duration(hours: 1))
.millisecondsSinceEpoch ~/
1000,
delegatedPub32: recipientPub32,
);
_add('issueCap', '${token.length} bytes token');
final valid = await vault.verifyCap(
capToken: token,
requesterPub32: recipientPub32,
);
_add('verifyCap', valid.toString(), ok: valid);
} finally {
await vault.dispose();
}
}
// ── Key derivation ───────────────────────────────────────────────────────
Future<void> _runKeyDerivation() async {
final phrase = await InfusionFFI.mnemonicGenerate(wordCount: 12);
final keys = jsonDecode(await InfusionFFI.mnemonicRestore(phrase))
as Map<String, dynamic>;
final vault = await InfusionFFI.create(
encKeyHex: keys['enc_key_hex'] as String,
signSeedHex: keys['sign_seed_hex'] as String,
);
try {
final context = Uint8List.fromList(utf8.encode('storage:user_box'));
final derived = await vault.deriveKey(context);
_add('deriveKey', '${derived.length} bytes: ${_short(_hex(derived))}');
final idx = await vault.blindIndex('user@example.com');
_add('blindIndex', '${idx.length} bytes: ${_short(_hex(idx))}');
} finally {
await vault.dispose();
}
}
// ── Hashing ──────────────────────────────────────────────────────────────
Future<void> _runHashing() async {
final data = Uint8List.fromList(utf8.encode('hello world'));
final cid = await InfusionFFI.cid(data);
_add('cid (BLAKE3)', _short(_hex(cid)));
final sha = await InfusionFFI.sha256(data);
_add('sha256', _short(_hex(sha)));
final shaHex = await InfusionFFI.sha256Hex('nonce_value');
_add('sha256Hex', _short(shaHex));
}
// ── Helpers ───────────────────────────────────────────────────────────────
void _add(String label, String value, {bool ok = true}) {
setState(() => _results.add(_Result(label: label, value: value, ok: ok)));
}
static String _short(String s) =>
s.length > 28 ? '${s.substring(0, 14)}…${s.substring(s.length - 6)}' : s;
static String _hex(Uint8List b) =>
b.map((e) => e.toRadixString(16).padLeft(2, '0')).join();
static Uint8List _fromHex(String hex) {
final result = Uint8List(hex.length ~/ 2);
for (var i = 0; i < result.length; i++) {
result[i] = int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16);
}
return result;
}
// ── UI ────────────────────────────────────────────────────────────────────
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('infusion_ffi example'),
backgroundColor: colorScheme.inversePrimary,
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: FilledButton.icon(
onPressed: _running ? null : _run,
icon: _running
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Icon(Icons.play_arrow_rounded),
label: Text(_running ? 'Running…' : 'Run all demos'),
),
),
Expanded(
child: ListView.separated(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 32),
itemCount: _results.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (_, i) {
final r = _results[i];
return ListTile(
dense: true,
leading: Icon(
r.ok ? Icons.check_circle : Icons.cancel,
color: r.ok ? Colors.green : Colors.red,
size: 20,
),
title: Text(
r.label,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
subtitle: Text(
r.value,
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
fontFamily: 'monospace',
),
),
);
},
),
),
],
),
);
}
}
class _Result {
const _Result({
required this.label,
required this.value,
required this.ok,
});
final String label;
final String value;
final bool ok;
}