kpqc 0.1.1
kpqc: ^0.1.1 copied to clipboard
Dart FFI APIs for AIMer, HAETAE, NTRU+, and SMAUG-T.
KpqC #
KpqC provides safe, synchronous Dart APIs for AIMer, HAETAE, NTRU+, and SMAUG-T.
Runtime support #
- Dart 3.13 or newer
- Linux, macOS, or Windows
- A supported C11 toolchain for the target platform
The package uses Dart build hooks to compile and bundle its native code. macOS
on Apple silicon is currently verified locally; desktop CI is configured for
Linux, macOS, and Windows. Android and iOS build targets are implemented but
remain unverified. Dart Web is not supported because the implementation uses
dart:ffi.
Use dart run, dart test, or dart build cli so that Dart runs the native
build hooks. The legacy dart compile exe command does not bundle code assets.
Install #
dart pub add kpqc
Available schemes #
| Algorithm | Type | Exports |
|---|---|---|
| AIMer | Signature | aimer128f, aimer128s, aimer192f, aimer192s, aimer256f, aimer256s |
| HAETAE | Signature | haetae2, haetae3, haetae5 |
| NTRU+ | Key encapsulation | ntruplus768, ntruplus864, ntruplus1152 |
| SMAUG‑T | Key encapsulation | smaugt128, smaugt192, smaugt256, timer |
Importing an algorithm family keeps the entry point focused:
import 'dart:convert';
import 'dart:typed_data';
import 'package:kpqc/aimer.dart';
void main() {
final payload = Uint8List.fromList(utf8.encode('release-manifest:v3'));
final keys = aimer128f.generateKeyPair();
try {
final proof = aimer128f.sign(payload, keys.secretKey);
if (!aimer128f.verify(payload, proof, keys.publicKey)) {
throw StateError('Signature verification failed');
}
} finally {
keys.dispose();
}
}
Signature contexts #
AIMer and HAETAE accept an optional context. A context separates signatures created for different application purposes and may contain up to 255 bytes.
import 'dart:convert';
import 'dart:typed_data';
import 'package:kpqc/haetae.dart';
void main() {
final payload = Uint8List.fromList(utf8.encode('account=42'));
final context = Uint8List.fromList(utf8.encode('audit-record'));
final keys = haetae3.generateKeyPair();
try {
final signature = haetae3.sign(
payload,
keys.secretKey,
context: context,
);
final valid = haetae3.verify(
payload,
signature,
keys.publicKey,
context: context,
);
print(valid); // true
} finally {
keys.dispose();
}
}
Verification fails when the supplied context does not match the one used for signing.
Key encapsulation #
A KEM creates a shared secret for a sender and a recipient. The public key may be distributed; the secret key and resulting shared secret must remain private.
import 'dart:typed_data';
import 'package:kpqc/smaugt.dart';
void main() {
final recipient = smaugt192.generateKeyPair();
try {
final outbound = smaugt192.encapsulate(recipient.publicKey);
try {
// Send outbound.ciphertext to the recipient.
final inboundSecret = smaugt192.decapsulate(
outbound.ciphertext,
recipient.secretKey,
);
try {
print(_sameBytes(inboundSecret, outbound.sharedSecret)); // true
} finally {
inboundSecret.fillRange(0, inboundSecret.length, 0);
}
} finally {
outbound.dispose();
}
} finally {
recipient.dispose();
}
}
bool _sameBytes(Uint8List left, Uint8List right) {
if (left.length != right.length) return false;
var difference = 0;
for (var index = 0; index < left.length; index++) {
difference |= left[index] ^ right[index];
}
return difference == 0;
}
Imports #
Each family has a dedicated library:
import 'package:kpqc/aimer.dart';
import 'package:kpqc/haetae.dart';
import 'package:kpqc/ntruplus.dart';
import 'package:kpqc/smaugt.dart';
All named algorithms are also exported from the package root:
import 'package:kpqc/kpqc.dart';
final SignatureAlgorithm signer = aimer192f;
final KeyEncapsulationAlgorithm keyExchange = ntruplus864;
Data and failures #
Inputs and outputs use Uint8List. Each algorithm exposes an id and a
sizes value. Incorrect key or ciphertext lengths throw
ArgumentError; native failures throw KpqCException. Signature verification
returns false for an invalid signature.
Parameter sizes #
All sizes are in bytes.
Signatures
| Algorithm | Public key | Secret key | Signature |
|---|---|---|---|
aimer128f |
32 | 48 | 5,888 |
aimer128s |
32 | 48 | 4,160 |
aimer192f |
48 | 72 | 13,056 |
aimer192s |
48 | 72 | 9,120 |
aimer256f |
64 | 96 | 25,120 |
aimer256s |
64 | 96 | 17,056 |
haetae2 |
992 | 1,408 | 1,474 |
haetae3 |
1,472 | 2,112 | 2,349 |
haetae5 |
2,080 | 2,752 | 2,948 |
Key encapsulation
| Algorithm | Public key | Secret key | Ciphertext | Shared secret |
|---|---|---|---|---|
ntruplus768 |
1,152 | 2,336 | 1,152 | 32 |
ntruplus864 |
1,296 | 2,624 | 1,296 | 32 |
ntruplus1152 |
1,728 | 3,488 | 1,728 | 32 |
smaugt128 |
672 | 832 | 672 | 32 |
smaugt192 |
1,088 | 1,312 | 992 | 32 |
smaugt256 |
1,440 | 1,728 | 1,376 | 32 |
timer |
672 | 832 | 608 | 32 |
Methods reject values of the wrong size. Signature verification returns false
for an invalid signature. NTRU+ rejects an invalid ciphertext. SMAUG-T performs
implicit rejection and returns a replacement secret instead; that value will
not equal the sender's shared secret.
Distribution #
The published package includes Dart libraries and the bundled native sources. Build hooks compile each parameter set with its upstream configuration macros, isolate its C symbols, and use Dart 3.13 link-time usage recording to bundle only the native assets used by the application.
KeyPair.dispose() and EncapsulatedSecret.dispose() overwrite their secret
byte lists on a best-effort basis. The caller is responsible for clearing the
list returned directly by decapsulate.
Building from source #
A C11 compiler and the Dart SDK are required for normal development:
dart pub get
dart analyze
dart test
Security #
The native cores are compiled from the upstream algorithm implementations. This package has not received an independent security audit and does not provide a constant-time execution guarantee. Assess those constraints before using it with sensitive production keys.
Third-party licenses and attributions are listed in THIRD_PARTY_NOTICES.md.