← Back to list

ffi-patterns
by djx-y-z
Dart FFI wrapper for liboqs, delivering post-quantum cryptographic algorithms. Supports NIST-standardized KEMs (e.g., ML-KEM) and signatures (e.g., ML-DSA), optimized for Flutter and cross-platform applications (Android, iOS, Linux, macOS, Windows). MIT Licensed.
⭐ 3🍴 1📅 Jan 18, 2026
SKILL.md
name: ffi-patterns description: Dart FFI patterns and best practices for this project. Use when writing FFI code, working with native memory, creating wrappers, or implementing new native bindings.
FFI Patterns for liboqs_dart
Patterns and templates for writing correct Dart FFI code in this project.
Memory Allocation
Allocate Bytes
final ptr = LibOQSUtils.allocateBytes(length);
try {
// Use ptr...
} finally {
LibOQSUtils.freePointer(ptr); // or secureFreePointer for secrets
}
Convert Uint8List to Pointer
final ptr = LibOQSUtils.uint8ListToPointer(data);
try {
// Use ptr...
} finally {
LibOQSUtils.freePointer(ptr);
}
Convert Pointer to Uint8List
final result = LibOQSUtils.pointerToUint8List(ptr, length);
// Result is a copy - safe to free ptr after this
String Handling
final namePtr = algorithmName.toNativeUtf8();
try {
final result = oqs.OQS_KEM_new(namePtr.cast());
// ...
} finally {
LibOQSUtils.freePointer(namePtr);
}
Wrapper Class Pattern
// Finalizer for automatic cleanup
final Finalizer<Pointer<oqs.OQS_KEM>> _kemFinalizer = Finalizer(
(ptr) => oqs.OQS_KEM_free(ptr),
);
class KEM {
late final Pointer<oqs.OQS_KEM> _ptr;
bool _disposed = false;
KEM._(this._ptr) {
// Attach finalizer for GC cleanup
_kemFinalizer.attach(this, _ptr, detach: this);
}
void _checkDisposed() {
if (_disposed) {
throw StateError('Instance has been disposed');
}
}
// Factory constructor
static KEM create(String algorithmName) {
LibOQSBase.init(); // Auto-initialize library
final namePtr = algorithmName.toNativeUtf8();
try {
final ptr = oqs.OQS_KEM_new(namePtr.cast());
if (ptr == nullptr) {
throw LibOQSException('Failed to create instance');
}
return KEM._(ptr);
} finally {
LibOQSUtils.freePointer(namePtr);
}
}
// Dispose pattern
void dispose() {
if (!_disposed) {
oqs.OQS_KEM_free(_ptr); // 1. Free native memory
_kemFinalizer.detach(this); // 2. Detach finalizer
_disposed = true; // 3. Set flag
}
}
}
Data Class with Secrets
final Finalizer<Uint8List> _secretFinalizer = Finalizer((data) {
LibOQSUtils.zeroMemory(data);
});
class KeyPair {
final Uint8List publicKey;
final Uint8List secretKey;
KeyPair({required this.publicKey, required this.secretKey}) {
// Auto-zero secret on GC (defense-in-depth)
_secretFinalizer.attach(this, secretKey, detach: this);
}
/// Zero secrets immediately (recommended)
void clearSecrets() {
LibOQSUtils.zeroMemory(secretKey);
}
/// Safe: only exposes public key
String get publicKeyBase64 => base64Encode(publicKey);
/// **Security Warning:** Exports SECRET KEY in plaintext!
Map<String, String> toStrings() {
return {
'publicKey': base64Encode(publicKey),
'secretKey': base64Encode(secretKey),
};
}
}
Calling Native Functions
From Struct Field
// 1. Validate function pointer
if (_ptr.ref.keypair == nullptr) {
throw LibOQSException('Function pointer is null');
}
// 2. Convert to Dart function
final keypairFn = _ptr.ref.keypair.asFunction<
int Function(Pointer<Uint8> publicKey, Pointer<Uint8> secretKey)
>();
// 3. Allocate output buffers
final publicKey = LibOQSUtils.allocateBytes(publicKeyLength);
final secretKey = LibOQSUtils.allocateBytes(secretKeyLength);
try {
// 4. Call function
final result = keypairFn(publicKey, secretKey);
if (result != 0) {
throw LibOQSException('Operation failed', result);
}
// 5. Copy results to Dart
return KeyPair(
publicKey: LibOQSUtils.pointerToUint8List(publicKey, publicKeyLength),
secretKey: LibOQSUtils.pointerToUint8List(secretKey, secretKeyLength),
);
} finally {
// 6. Free native memory (secure for secrets!)
LibOQSUtils.freePointer(publicKey);
LibOQSUtils.secureFreePointer(secretKey, secretKeyLength);
}
Direct Library Call
final count = oqs.OQS_KEM_alg_count(); // Simple - no cleanup needed
final namePtr = oqs.OQS_KEM_alg_identifier(i);
if (namePtr != nullptr) {
final name = namePtr.cast<Utf8>().toDartString();
// Don't free namePtr - it's a static string from library
}
Error Handling
try {
final result = nativeFunction(args);
if (result != 0) {
throw LibOQSException('Operation failed', result);
}
} on LibOQSException {
rethrow; // Preserve our exceptions
} catch (e) {
throw LibOQSException('Unexpected error: $e');
}
Utilities Reference
| Method | Use For |
|---|---|
LibOQSUtils.allocateBytes(n) | Allocate n bytes |
LibOQSUtils.freePointer(ptr) | Free non-sensitive memory |
LibOQSUtils.secureFreePointer(ptr, len) | Free sensitive memory (zeros first) |
LibOQSUtils.uint8ListToPointer(list) | Copy Dart list to native |
LibOQSUtils.pointerToUint8List(ptr, len) | Copy native to Dart list |
LibOQSUtils.zeroMemory(list) | Zero a Dart Uint8List |
LibOQSUtils.constantTimeEquals(a, b) | Compare secrets safely |
LibOQSUtils.validateAlgorithmName(name) | Check algorithm name format |
Score
Total Score
75/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
✓説明文
100文字以上の説明がある
+10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon

