encryptStream method
Future<PqStreamingStats>
encryptStream({
- required Uint8List recipientPublicKey,
- required Stream<
List< source,int> > - required File output,
- required PqForgeProfile profile,
- Uint8List? recipientKexPublicKey,
- List<
PqRecipientSpec> additionalRecipients = const [], - String? recipientKeyId,
- Uint8List? aad,
- Map<
String, Object?> metadata = const {}, - Uint8List? signerSecretKey,
- PqSignatureAlgorithm? signatureAlgorithm,
- String? signerKeyId,
- int frameSize = PqStreamingEnvelope.defaultFrameSize,
Streams arbitrary plaintext source bytes into a .pqfs container at
output — same container, framing, and signing as encryptFile, but the
input never has to exist as a file. This is what lets pack seal a whole
folder without ever spooling plaintext to disk.
The total length need not be known up front: a one-frame lookahead decides
which frame is final. Peak memory ≈ 2 × frameSize.
Implementation
Future<PqStreamingStats> encryptStream({
required Uint8List recipientPublicKey,
required Stream<List<int>> source,
required File output,
required PqForgeProfile profile,
Uint8List? recipientKexPublicKey,
List<PqRecipientSpec> additionalRecipients = const [],
String? recipientKeyId,
Uint8List? aad,
Map<String, Object?> metadata = const {},
Uint8List? signerSecretKey,
PqSignatureAlgorithm? signatureAlgorithm,
String? signerKeyId,
int frameSize = PqStreamingEnvelope.defaultFrameSize,
}) async {
final context = await _prepareEncrypt(
recipientPublicKey: recipientPublicKey,
recipientKexPublicKey: recipientKexPublicKey,
additionalRecipients: additionalRecipients,
recipientKeyId: recipientKeyId,
profile: profile,
aad: aad,
metadata: metadata,
signerSecretKey: signerSecretKey,
signatureAlgorithm: signatureAlgorithm,
signerKeyId: signerKeyId,
frameSize: frameSize,
);
return _writeContainer(context, output, (writer) async {
// (No read-ahead here: the source is already an async stream, so the
// producer naturally runs ahead while a frame is awaited.)
final frame = Uint8List(frameSize);
var filled = 0;
Uint8List? readyFrame; // full frame held until finality is known
await for (final chunk in source) {
var offset = 0;
while (offset < chunk.length) {
final n = (frameSize - filled) < (chunk.length - offset)
? frameSize - filled
: chunk.length - offset;
frame.setRange(filled, filled + n, chunk, offset);
filled += n;
offset += n;
if (filled == frameSize) {
if (readyFrame != null) {
await writer.add(readyFrame, isFinal: false);
}
readyFrame = Uint8List.fromList(frame);
filled = 0;
}
}
}
if (readyFrame != null) {
if (filled == 0) {
await writer.add(readyFrame, isFinal: true);
} else {
await writer.add(readyFrame, isFinal: false);
await writer.add(
Uint8List.sublistView(frame, 0, filled),
isFinal: true,
);
}
} else {
// Covers both a short (<1 frame) stream and a fully empty one.
await writer.add(
Uint8List.sublistView(frame, 0, filled),
isFinal: true,
);
}
});
}