main function
Implementation
Future<void> main() async {
print('=== Avvio demo CypherMask con socket in memoria ===\n');
final link = InMemoryDuplex();
final mask = CypherMask(aead: AeadAlgorithm.chacha20Poly1305);
print('[SETUP] Alice e Bob generano le identità...');
final alice = await Identity.generate();
final bob = await Identity.generate();
final (bobBundle, bobSecret) = await PreKeys.generate(identity: bob, count: 8);
print('[SETUP] Bob pubblica un PreKeyBundle con SPK e OPK.\n');
print('[HANDSHAKE] Alice prepara init X3DH usando il bundle di Bob...');
final (sx, _) = await mask.startSession(me: alice, peerBundle: bobBundle);
final initFrame = await mask.buildInitFrame(
me: alice,
sessionX3DH: sx,
includeEd25519Pub: true,
signTranscript: true,
);
late final Session bobSession;
final cBobInit = Completer<void>();
final subInitBob = link.streamToBob.listen((bytes) async {
final m = json.decode(utf8.decode(bytes));
if (m['type'] != 'x3dh_init') return;
print('[HANDSHAKE] Bob ha ricevuto initFrame: verifica firma e costruisce la sessione...');
final parsed = CypherMask.parseInitFrame(bytes);
if (parsed.sigEd25519 != null && parsed.initiatorEd25519 != null) {
final ok = await CypherMask.verifyInitSignature(
initPacket: parsed.initPacket,
signatureBytes: parsed.sigEd25519!,
ed25519Pub: parsed.initiatorEd25519!,
);
print('[HANDSHAKE] Verifica firma di Alice: $ok');
if (!ok) throw StateError('Init signature verification failed');
}
bobSession = await mask.acceptSession(
me: bob,
localPreKeys: (bobBundle, bobSecret),
initPacket: parsed.initPacket,
initiatorStaticX25519: parsed.initiatorIkx,
);
print('[HANDSHAKE] Sessione Bob inizializzata.\n');
cBobInit.complete();
});
print('[HANDSHAKE] Alice invia initFrame a Bob.');
link.sendFromAlice(initFrame);
await cBobInit.future;
await subInitBob.cancel();
final aliceSession = sx.session;
final cMsg = Completer<void>();
late final StreamSubscription<Uint8List> subMsgBob;
subMsgBob = link.streamToBob.listen((bytes) async {
final m = json.decode(utf8.decode(bytes));
if (m['type'] != 'msg') return;
print('[MSG] Bob riceve un messaggio, decifra...');
final p = CypherMask.parseMsgFrame(bytes);
final text = await mask.decryptText(bobSession, p);
print('>>> Bob ha ricevuto: "$text"');
final reply = await mask.encryptText(bobSession, 'Ricevuto, grazie.');
print('[MSG] Bob risponde con un ACK.');
link.sendFromBob(CypherMask.buildMsgFrame(reply));
cMsg.complete();
});
print('[MSG] Alice cifra e invia un messaggio a Bob...');
final hello = await mask.encryptText(aliceSession, 'Ciao Bob, messaggio segreto.');
link.sendFromAlice(CypherMask.buildMsgFrame(hello));
await cMsg.future;
await subMsgBob.cancel();
final cReply = Completer<void>();
late final StreamSubscription<Uint8List> subMsgAlice;
subMsgAlice = link.streamToAlice.listen((bytes) async {
final m = json.decode(utf8.decode(bytes));
if (m['type'] != 'msg') return;
final p = CypherMask.parseMsgFrame(bytes);
final text = await mask.decryptText(aliceSession, p);
print('>>> Alice ha ricevuto: "$text"\n');
cReply.complete();
});
await cReply.future;
await subMsgAlice.cancel();
print('[CALL] Avvio demo media E2EE: Alice invia 100 frame Opus fittizi a Bob...');
int mediaEpoch = 0;
int mediaCounter = 0;
final totalFrames = 100;
final mediaDone = Completer<void>();
int framesReceived = 0;
int bytesReceived = 0;
late final StreamSubscription<Uint8List> subMediaBob;
subMediaBob = link.streamToBob.listen((bytes) async {
final m = json.decode(utf8.decode(bytes));
if (m['type'] != 'media_chunk') return;
final parsed = CypherMask.parseMediaChunkFrame(bytes);
final raw = await mask.decryptMediaFrame(
receiver: bobSession,
packet: parsed.packet,
counter: parsed.counter,
epoch: parsed.epoch,
codec: parsed.codec,
timestampMs: parsed.timestampMs,
);
framesReceived += 1;
bytesReceived += raw.length;
if (framesReceived % 20 == 0) {
print('[CALL] Bob ha decifrato $framesReceived/$totalFrames frame '
'(bytes cumulati: $bytesReceived)');
}
if (framesReceived == totalFrames) {
print('[CALL] Bob ha terminato la ricezione dei frame (tot bytes: $bytesReceived).');
mediaDone.complete();
}
});
final rnd = Random(42);
for (var i = 0; i < totalFrames; i++) {
if (i == 50) {
mediaEpoch += 1;
print('[CALL] Rotazione epoch → $mediaEpoch');
}
final size = 60 + rnd.nextInt(61);
final opusLike = Uint8List.fromList(List<int>.generate(size, (_) => rnd.nextInt(256)));
mediaCounter += 1;
final pkt = await mask.encryptMediaFrame(
sender: aliceSession,
encodedFrame: opusLike,
counter: mediaCounter,
epoch: mediaEpoch,
codec: 'opus',
timestampMs: DateTime.now().millisecondsSinceEpoch,
);
final wire = CypherMask.buildMediaChunkFrame(
packet: pkt,
counter: mediaCounter,
epoch: mediaEpoch,
codec: 'opus',
timestampMs: DateTime.now().millisecondsSinceEpoch,
);
link.sendFromAlice(wire);
await Future<void>.delayed(const Duration(milliseconds: 2));
}
await mediaDone.future;
await subMediaBob.cancel();
// ======== FILE ========
print('[FILE] Alice prepara un file di 300KB e lo cifra in chunk...');
final rndFile = Random(7);
final fileBytes = Uint8List.fromList(
List<int>.generate(300 * 1024, (_) => rndFile.nextInt(256)),
);
final send = await mask.encryptFile(
sender: aliceSession,
fileBytes: fileBytes,
fileName: 'demo.bin',
mimeType: 'application/octet-stream',
chunkSize: 64 * 1024,
);
FileManifest? manifest;
final received = <int, Packet>{};
final cFile = Completer<void>();
late final StreamSubscription<Uint8List> subFileBob;
subFileBob = link.streamToBob.listen((bytes) async {
final m = json.decode(utf8.decode(bytes));
switch (m['type']) {
case 'file_manifest':
manifest = CypherMask.parseFileManifestFrame(bytes);
print('[FILE] Bob riceve manifest: '
'file="${manifest!.fileName}", size=${manifest!.fileSize}B, chunks=${manifest!.chunks}');
break;
case 'file_chunk':
final part = CypherMask.parseFileChunkFrame(bytes);
print('[FILE] Bob riceve chunk ${part.index + 1}/${part.total}');
received[part.index] = part.packet;
if (manifest != null && received.length == manifest!.chunks) {
final ordered = List<Packet>.generate(
manifest!.chunks,
(i) => received[i]!,
);
final out = await mask.decryptFile(
receiver: bobSession,
manifest: manifest!,
chunks: ordered,
);
print('[FILE] Bob ricostruisce il file: ${out.length} bytes '
'(ok=${out.length == fileBytes.length})');
cFile.complete();
}
break;
}
});
print('[FILE] Alice invia il manifest a Bob.');
link.sendFromAlice(CypherMask.buildFileManifestFrame(send));
for (var i = 0; i < send.wire.length; i++) {
final p = Packet.fromBytes(send.wire[i].bytes);
print('[FILE] Alice invia chunk ${i + 1}/${send.wire.length}');
link.sendFromAlice(CypherMask.buildFileChunkFrame(
chunk: p,
index: i,
total: send.wire.length,
));
}
await cFile.future;
await subFileBob.cancel();
await link.close();
print('\n=== Demo terminata con successo ===');
}