pqcrypto 0.4.0 copy "pqcrypto: ^0.4.0" to clipboard
pqcrypto: ^0.4.0 copied to clipboard

Pure Dart post-quantum cryptography. Zero dependencies. ML-KEM, ML-DSA, and SLH-DSA with checked-in NIST vector evidence.

example/main.dart

// ignore_for_file: avoid_print

import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';

import 'package:pqcrypto/pqcrypto.dart';

void main() {
  _printIntro();

  _runMlKemExamples();
  _runMlDsaExamples();
  _runSlhDsaExamples();
  _runSignedHandshakeExample();

  print('\nDone.');
}

void _printIntro() {
  print('pqcrypto 0.4.0 example');
  print('Pure Dart NIST PQC primitives with zero runtime dependencies.');
  print('Families shown: ML-KEM, ML-DSA, and SLH-DSA.');
  print(
    'Evidence is KAT/ACVP/interop conformance, not CMVP/FIPS 140 validation.',
  );
}

void _runMlKemExamples() {
  _section('1. ML-KEM (FIPS 203): key encapsulation');
  print('ML-KEM establishes a shared secret; it is not encryption by itself.');
  _printRowHeader(<String>['Set', 'PK', 'SK', 'CT', 'SS', 'Match']);

  for (final example in <({String name, KyberKem kem})>[
    (name: 'ML-KEM-512', kem: PqcKem.kyber512),
    (name: 'ML-KEM-768', kem: PqcKem.kyber768),
    (name: 'ML-KEM-1024', kem: PqcKem.kyber1024),
  ]) {
    final (publicKey, secretKey) = example.kem.generateKeyPair();
    final (ciphertext, clientSecret) = example.kem.encapsulate(publicKey);
    final serverSecret = example.kem.decapsulate(secretKey, ciphertext);
    final ok = _bytesEqual(clientSecret, serverSecret);

    _printRow(<Object>[
      example.name,
      publicKey.length,
      secretKey.length,
      ciphertext.length,
      clientSecret.length,
      ok,
    ]);

    if (!ok) {
      throw StateError('${example.name} shared secrets did not match');
    }
  }
}

void _runMlDsaExamples() {
  _section('2. ML-DSA (FIPS 204): digital signatures');
  print('ML-DSA signs byte messages with an optional FIPS context string.');
  _printRowHeader(<String>['Set', 'PK', 'SK', 'SIG', 'Valid', 'Wrong ctx']);

  for (final example in <({String name, DilithiumParams params})>[
    (name: 'ML-DSA-44', params: DilithiumParams.mlDsa44),
    (name: 'ML-DSA-65', params: DilithiumParams.mlDsa65),
    (name: 'ML-DSA-87', params: DilithiumParams.mlDsa87),
  ]) {
    final (publicKey, secretKey) = MlDsa.generateKeyPair(example.params);
    final message = _utf8Bytes('pqcrypto ML-DSA demo message');
    final context = _utf8Bytes('pqcrypto-example-mldsa-v1');
    final signature = MlDsa.sign(
      secretKey,
      message,
      example.params,
      ctx: context,
    );
    final ok = MlDsa.verify(
      publicKey,
      message,
      signature,
      example.params,
      ctx: context,
    );
    final wrongContextOk = MlDsa.verify(
      publicKey,
      message,
      signature,
      example.params,
      ctx: _utf8Bytes('wrong-context'),
    );

    _printRow(<Object>[
      example.name,
      publicKey.length,
      secretKey.length,
      signature.length,
      ok,
      wrongContextOk,
    ]);

    if (!ok || wrongContextOk) {
      throw StateError('${example.name} signature verification failed');
    }
  }
}

void _runSlhDsaExamples() {
  _section('3. SLH-DSA (FIPS 205): stateless hash-based signatures');
  print('All 12 FIPS 205 parameter sets are supported.');
  print('The slow "s" sets are listed but not signed in this quick example.');
  _printRowHeader(<String>['Set', 'Cat', 'Hash', 'Mode', 'PK', 'SK', 'SIG']);

  for (final params in SlhDsaParams.supportedValues) {
    _printRow(<Object>[
      params.name,
      params.securityCategory,
      params.hashFamily.name.toUpperCase(),
      params.isFast ? 'fast' : 'small',
      params.publicKeyBytes,
      params.secretKeyBytes,
      params.signatureBytes,
    ]);
  }

  print('\nRunnable fast-set sign/verify checks:');
  _runSlhDsaSignExample(SlhDsaParams.sha2128f);
  _runSlhDsaSignExample(SlhDsaParams.shake128f);
}

void _runSlhDsaSignExample(SlhDsaParams params) {
  final (publicKey, secretKey) = SlhDsa.generateKeyPair(params);
  final message = _utf8Bytes('pqcrypto SLH-DSA demo message');
  final context = _utf8Bytes('pqcrypto-example-slhdsa-v1');
  final signature = SlhDsa.sign(
    secretKey,
    message,
    params,
    context: context,
    verifyAfterSign: true,
  );
  final ok = SlhDsa.verify(
    publicKey,
    message,
    signature,
    params,
    context: context,
  );
  final hashSignature = SlhDsa.hashSignDeterministic(
    secretKey,
    message,
    SlhDsaPreHash.sha3256,
    params,
    context: context,
  );
  final hashOk = SlhDsa.hashVerify(
    publicKey,
    message,
    hashSignature,
    SlhDsaPreHash.sha3256,
    params,
    context: context,
  );

  print(
    '  ${params.name}: sig=${signature.length}, pureOk=$ok, '
    'hashSig=${hashSignature.length}, hashOk=$hashOk',
  );

  if (!ok || !hashOk) {
    throw StateError('${params.name} SLH-DSA verification failed');
  }
}

void _runSignedHandshakeExample() {
  _section('4. ML-KEM + ML-DSA: signed handshake transcript');
  print('This composes primitives only. Applications still need KDF + AEAD,');
  print('authenticated key directories, replay storage, and session policy.');

  final kem = PqcKem.kyber768;
  final dsaParams = DilithiumParams.mlDsa65;
  final context = _utf8Bytes('pqcrypto-handshake-v1');

  final (serverKemPublicKey, serverKemSecretKey) = kem.generateKeyPair();
  final (clientSigningPublicKey, clientSigningSecretKey) =
      MlDsa.generateKeyPair(dsaParams);

  final clientNonce = _randomBytes(32);
  final timestampMs = DateTime.now().toUtc().millisecondsSinceEpoch;
  final (ciphertext, clientSecret) = kem.encapsulate(serverKemPublicKey);

  final transcript = _buildTranscript(<Uint8List>[
    _utf8Bytes('pqcrypto-example/signed-handshake/v1'),
    _utf8Bytes('ML-KEM-768'),
    _utf8Bytes('ML-DSA-65'),
    _utf8Bytes('server-key-epoch-1'),
    _uint64(timestampMs),
    clientNonce,
    serverKemPublicKey,
    clientSigningPublicKey,
    ciphertext,
  ]);

  final clientSignature = MlDsa.sign(
    clientSigningSecretKey,
    transcript,
    dsaParams,
    ctx: context,
  );
  final signatureOk = MlDsa.verify(
    clientSigningPublicKey,
    transcript,
    clientSignature,
    dsaParams,
    ctx: context,
  );
  final serverSecret = kem.decapsulate(serverKemSecretKey, ciphertext);
  final secretOk = _bytesEqual(clientSecret, serverSecret);

  _printRowHeader(<String>['Field', 'Value']);
  _printRow(<Object>['Transcript bytes', transcript.length]);
  _printRow(<Object>['Client nonce prefix', '${_hex(clientNonce.take(8))}...']);
  _printRow(<Object>['Shared secret match', secretOk]);
  _printRow(<Object>['Transcript signature', signatureOk]);

  if (!secretOk || !signatureOk) {
    throw StateError('Signed handshake example failed');
  }
}

void _section(String title) {
  print('\n$title');
  print('-' * title.length);
}

void _printRowHeader(List<String> values) {
  print('  ${_formatColumns(values)}');
}

void _printRow(List<Object> values) {
  print('  ${_formatColumns(values.map((value) => '$value').toList())}');
}

String _formatColumns(List<String> values) {
  const widths = <int>[22, 8, 8, 8, 10, 10, 10];
  final cells = <String>[];
  for (var i = 0; i < values.length; i++) {
    final width = widths[i < widths.length ? i : widths.length - 1];
    cells.add(values[i].padRight(width));
  }
  return cells.join(' ');
}

Uint8List _buildTranscript(List<Uint8List> fields) {
  final framed = <Uint8List>[];
  for (final field in fields) {
    framed
      ..add(_uint32(field.length))
      ..add(field);
  }
  return _concat(framed);
}

Uint8List _concat(List<Uint8List> chunks) {
  final total = chunks.fold<int>(0, (sum, chunk) => sum + chunk.length);
  final out = Uint8List(total);
  var offset = 0;
  for (final chunk in chunks) {
    out.setRange(offset, offset + chunk.length, chunk);
    offset += chunk.length;
  }
  return out;
}

bool _bytesEqual(Uint8List a, Uint8List b) {
  if (a.length != b.length) return false;
  var diff = 0;
  for (var i = 0; i < a.length; i++) {
    diff |= a[i] ^ b[i];
  }
  return diff == 0;
}

Uint8List _randomBytes(int length) {
  final rng = Random.secure();
  final out = Uint8List(length);
  for (var i = 0; i < out.length; i++) {
    out[i] = rng.nextInt(256);
  }
  return out;
}

Uint8List _uint32(int value) {
  return Uint8List(4)..buffer.asByteData().setUint32(0, value, Endian.big);
}

Uint8List _uint64(int value) {
  return Uint8List(8)..buffer.asByteData().setUint64(0, value, Endian.big);
}

Uint8List _utf8Bytes(String value) => Uint8List.fromList(utf8.encode(value));

String _hex(Iterable<int> bytes) {
  return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
}
4
likes
160
points
2.76k
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Pure Dart post-quantum cryptography. Zero dependencies. ML-KEM, ML-DSA, and SLH-DSA with checked-in NIST vector evidence.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#cryptography #post-quantum #security #dart #flutter

License

MIT (license)

More

Packages that depend on pqcrypto